import { makeAutoObservable } from 'mobx';
import {} from 'openai';
import { Chat, List, getErrorMessage } from '..';
import { getUniqueId } from '../utils/string/getUniqueId';
import { ChatHistory } from './Chat';
import { ChatCompletionMessageParam } from 'openai/resources/index.mjs';

enum OpenAIErrorCodes {
  invalidRequest = 'invalid-request',
  contextLengthExceeded = 'context_length_exceeded'
}

export interface OpenAiChatApi {
  chatCompletion: (history: ChatHistory) => Promise<any>;
  createAudioTranscription: (file: File) => Promise<any>;
  uploadFile: (file: File) => Promise<any>;
}

export type ChatCompletionMessage = ChatCompletionMessageParam & {
  id: string;
};

export type ChatRoomHistory = List<ChatCompletionMessage>;

export class OpenAiChat {
  chat: Chat;
  loadingAiResponse: boolean;
  api: OpenAiChatApi;

  constructor(api: OpenAiChatApi) {
    this.api = api;
    const openAiUsers = [
      { id: 'user', name: 'User', role: 'user' },
      { id: 'assistant', name: 'Assistant', role: 'assistant' },
      { id: 'error', name: 'Error', role: 'system' },
      { id: 'system', name: 'System', role: 'system' }
    ];
    this.chat = new Chat(openAiUsers, []);

    this.loadingAiResponse = false;

    this.chat.addMessage({
      id: getUniqueId(),
      userId: 'assistant',
      content: 'Hello, how can I help you today?'
    });
    makeAutoObservable(this);
  }

  get lastAssistantMessageIndex() {
    const lastAssistantMessageIndex = this.history.values.map(
      (message, index) => {
        if (message.userId === 'assistant') {
          return index;
        }
        return 0;
      }
    );
    return lastAssistantMessageIndex[lastAssistantMessageIndex.length - 1];
  }

  get history() {
    return this.chat.history;
  }

  /**
   * The api does not include an id for the message, so we strip these out
   */
  get chatAiChatHistory() {
    return this.chat.history.values.map(({ id, ...message }) => message);
  }

  addMessage = (message: ChatCompletionMessage) => {
    this.history.set({
      id: message.id,
      userId: message.role,
      content: message.content || ('' as any)
    });
  };

  requestAudioTranscription = async (file: File) => {
    this.addMessage({
      id: getUniqueId(),
      role: 'user',
      content: `request transcription for ${file.name}`
    });
    this.setAiLoading(true);
    const response = await this.api.createAudioTranscription(file);

    if (response.type === 'error') {
      this.setAiLoading(false);
      this.addMessage({
        id: getUniqueId(),
        role: 'system',
        content: response.message || ''
      });
    } else {
      this.setAiLoading(false);
      this.addMessage({
        id: getUniqueId(),
        role: 'assistant',
        content: response.message || ''
      });
    }
  };

  uploadFile = async (file: File) => {
    this.addMessage({
      id: getUniqueId(),
      role: 'user',
      content: `uploading file for fine tunning ${file.name}`
    });
    this.setAiLoading(true);
    try {
      const response = await this.api.uploadFile(file);
      if (response?.type === 'error') {
        this.handleErrorMessage(response);
        this.setAiLoading(false);
      } else {
        this.setAiLoading(false);
        this.addMessage({
          id: getUniqueId(),
          role: 'assistant',
          content: String(response?.message) || ''
        });
      }
    } catch (error) {
      this.handleErrorMessage(error);
      this.setAiLoading(false);
    }
  };

  sendUserMessage = async (message: string) => {
    this.addMessage({ id: getUniqueId(), role: 'user', content: message });
    this.setAiLoading(true);
    try {
      const response = await this.api.chatCompletion(this.history);
      if (response.type === 'error') {
        this.handleErrorMessage(response);
        try {
          this.setAiLoading(false);
        } catch (error) {
          console.log(error);
        }
      } else {
        this.addMessage({
          id: getUniqueId(),
          role: 'assistant',
          content: response.message || ''
        });
        this.setAiLoading(false);
      }
    } catch (error) {
      this.handleErrorMessage(error);
      this.setAiLoading(false);
    }
  };

  setAiLoading = (loading: boolean) => {
    this.loadingAiResponse = loading;
  };

  /**
   * Handle error when content length is exceeded
   * code: "context_length_exceeded"
   * message: "This model's maximum context length is 4097 tokens. However, your messages resulted in 4176 tokens. Please reduce the length of the messages."
   * param: "messages"
   * type: "invalid_request_error"
   */
  private handleErrorContentLengthExceeded = (message?: string) => {
    // When the user sends a message it takes into account the messages
    // in the history.
    // Lets try to make the history smaller by clearing old messages and send the message again.
    if (this.history.length > 1) {
      this.history.shift();
      // let's try to send the message again
      this.sendUserMessage(message || '');
    } else {
      this.handleErrorMessage({
        code: 'context_length_exceeded',
        type: 'invalid_request_error',
        message:
          "This model's maximum context length is 4097 tokens. However, your messages resulted in 4176 tokens. Please reduce the length of the messages."
      });
    }
  };

  private resolveErrorMessage = (error: any) => {
    const message = getErrorMessage(error);

    this.addMessage({
      id: getUniqueId(),
      role: 'system',
      content: message || ''
    });
    return message;
  };

  private handleErrorMessage = async (error: any, text?: string) => {
    if (error.code === OpenAIErrorCodes.contextLengthExceeded) {
      this.handleErrorContentLengthExceeded(text);
      return;
    }

    return this.resolveErrorMessage(error);
  };
}
