import { makeAutoObservable } from 'mobx';
import { fetchMe } from '../utils/network/fetchMe';
import { getErrorMessage } from '../utils/Error';
import OpenAI, { ClientOptions } from 'openai';

export interface SpeechCreateParams {
  voice: string;
}

const APPHOUSE_PROD_AI_ENDPOINT = 'us-central1-apphouse-cc674';
export const TEST_HOST = 'http://127.0.0.1:5001/apphouse-cc674/us-central1/';
export const PROD_HOST = `https://${APPHOUSE_PROD_AI_ENDPOINT}.cloudfunctions.net/`;
export const DEFAULT_AUDIO_VOICE = 'alloy';

interface OpenAiFunctions {
  getThemeColorsV2: 'getThemeColorsV2';
  getDocumentAppConfig: 'getDocumentAppConfig';
  getDocumentAppForm: 'getDocumentAppForm';
  getDocumentTemplate: 'getDocumentTemplate';
  askOpenAi: 'askOpenAi';
  getAiImage: 'getAiImage';
}

export interface AssistantResponse {
  type: 'success' | 'error';
  message: string;
}

interface OpenAiFetchProps {
  /**
   * If url is not provided, the function name will be used to construct the url
   * @example https://us-central1-my-project.cloudfunctions.net/my-function
   */
  url?: string;
  /**
   * The prompt to be sent to the openAi api
   */
  prompt?: string;
  /**
   * The name of the function to be called
   */
  functionName: keyof OpenAiFunctions;
}

/**
 * OpenAi class to be used to fetch data from the openai api
 */
export class OpenAi {
  response: any = null;
  loading = false;
  error: string | null = null;
  host: string;
  openaiClient?: OpenAI;

  constructor(
    prod = false,
    host?: string,
    configuration?: ClientOptions | undefined
  ) {
    this.loading = false;
    this.error = null;
    this.host = host || prod ? PROD_HOST : TEST_HOST;
    this.openaiClient = undefined;
    if (configuration) {
      this.init(configuration);
    }
    makeAutoObservable(this);
  }

  setHost(host: string) {
    this.host = host;
  }

  /**
   * Ask the openai api a question
   * @param request the request to send to the openai api
   * @param resource any extra background information to provide to the api
   * @returns
   */
  ask = async (request: string, resource?: string) => {
    const response = this.fetch({
      prompt: JSON.stringify({
        resource: resource || 'your best bet',
        requirement: request
      }),
      functionName: 'askOpenAi'
    });
    return response;
  };

  fetch = async (fetchProps: OpenAiFetchProps) => {
    const { functionName, url, prompt } = fetchProps;
    const apiUrl = (url || this.host) + functionName;

    console.log({ apiUrl });
    this.loading = true;

    const response = await fetchMe(apiUrl, { prompt });
    this.loading = false;
    console.log(response);
    if (typeof response === 'string') {
      this.error = response;
      return undefined;
    } else {
      try {
        const res = response?.json();
        return res?.then((r) => {
          console.log(r);
          if (r?.message) {
            try {
              return JSON.parse(r?.message);
            } catch (e) {
              console.log(e);
              return r;
            }
          } else {
            console.log(r);
            return r;
          }
        });
      } catch (e) {
        console.log(e);
        return getErrorMessage(e);
      }
    }
  };

  /**
   * Initialize openAi Client
   * @param config the openai client configuration
   */
  init = (config: ClientOptions) => {
    this.openaiClient = new OpenAI(config);
  };

  /**
   * Convert text to audio and start downloading mp3 file
   * @param text The text to convert to audio
   */
  toAudio = async (
    text: string,
    voiceSelection: SpeechCreateParams['voice']
  ) => {
    const voice = voiceSelection as any;
    try {
      const response = await this.openaiClient?.audio.speech.create(
        {
          input: text,
          model: 'tts-1',
          voice: voice,
          response_format: 'mp3'
        },
        { __binaryResponse: true }
      );
      console.log('toAudio response', response);
      // convert readable stream to blob from response and create a url for user to download
      const stream = response?.body;
      if (stream) {
        const reader = stream.getReader();
        const chunks = [];
        let done = false;
        while (!done) {
          const { value, done: doneValue } = await reader.read();
          done = doneValue;
          if (value) {
            // @ts-ignore
            chunks.push(value);
          }
        }
        const blob = new Blob(chunks, { type: 'audio/mp3' });
        const url = URL.createObjectURL(blob);

        return { blob, url };
      }
    } catch (error) {
      console.error('Error converting text to audio:', error);
      // Handle the error as needed
      return {
        type: 'error',
        message: getErrorMessage(error) || 'Unknown error'
      };
    }
  };

  /**
   * Create a transcription of an audio file
   * @param file the audio file to transcribe
   * @returns the transcription of the audio file
   */
  createAudioTranscription = async (file: File): Promise<AssistantResponse> => {
    try {
      await this.openaiClient?.audio.transcriptions
        .create({
          model: 'whisper-1',
          file: file
        })
        .then((res) => {
          console.log('res', res);
          return {
            type: 'success' as any,
            message: res?.text || ''
          };
        })
        .catch((err) => {
          console.log('error', { err });
          const e = {
            type: 'error' as any,
            message: getErrorMessage(err) || 'Unknown error'
          };
          return e;
        });
    } catch (error) {
      return {
        type: 'error',
        message: getErrorMessage(error) || 'Unknown error'
      };
    }
    return {
      type: 'error',
      message: 'Unknown error'
    };
  };
}

export const openAi = new OpenAi(true);
/**
 * A hook to use the openai instance.
 * Note: This is a singleton object and should be used with caution.
 * This will be a global object and will be shared across the application.
 * @returns the openai instance
 */
export const useOpenAi = (config?: ClientOptions) => {
  if (config) {
    openAi.init(config);
  }
  return openAi;
};
