import logger from '../logger';
import { OpenAI, toFile } from 'openai';
import { ChatCompletionMessageParam } from 'openai/resources';
import { AIConfig } from '../config';
import { ChatCompletion } from 'openai/src/resources/chat/completions';
import { ResponseInput, Tool } from "openai/resources/responses/responses";

export class OpenaiService {

  constructor() {
  }

  async sendChatWithTools(
      messageList: ResponseInput,
      responseType: any = 'text',
      tools?: Array<Tool>
  ): Promise<string> {

    const openAICLient = new OpenAI({
      baseURL: AIConfig.ChatConfig.baseURL,
      apiKey: AIConfig.ChatConfig.apiKey,
    });
    const model = AIConfig.ChatConfig.model;

    logger.info(`[OpenAI] Sending ${messageList.length} messages`);
    logger.debug(`[OpenAI] Sending Msg: ${JSON.stringify(messageList[messageList.length - 1])}`);

    const responseResult = await openAICLient.responses.create({
      model: model,
      input: messageList,
      text: {
        format: {
          type: responseType
        }
      },
      reasoning: {},
      tools: tools,
      temperature: 1,
      max_output_tokens: 2048,
      top_p: 1,
      store: true
    });

    logger.debug('[OpenAI] Completion Response:' + JSON.stringify(responseResult.output_text));

    const messageResult = responseResult.output_text;

    const functionCalls = responseResult.output.filter(toolCall => toolCall.type === "function_call");
    if (functionCalls.length === 0)
      return messageResult;

    let updatedMessages: ResponseInput = [...messageList as any];

    for (const toolCall of responseResult.output) {
      if (toolCall.type !== "function_call") {
        continue;
      }

      updatedMessages.push(toolCall);

      // TODO: I have not implemented functions yet, so they will not be processed.
      //const name = toolCall.name;
      //const args = JSON.parse(toolCall.arguments);
      //
      // try {
      //
      //   // const result = await executeFunctions(name, args, inputData);
      //   updatedMessages.push({
      //     type: "function_call_output",
      //     call_id: toolCall.call_id,
      //     output: result.toString()
      //   });
      // }catch (error) {
      //   logger.error(`Error executing function ${name}:`, error);
      //   updatedMessages.push({
      //     type: "function_call_output",
      //     call_id: toolCall.call_id,
      //     output: `Error executing function ${name}.`
      //   });
      // }
    }

    // Recursive call with updated messages
    return this.sendChatWithTools(updatedMessages, responseType, tools);
  }

  /**
   * Sends a series of messages to the OpenAI Chat Completion API and retrieves a generated completion.
   * This function is designed to interact with the OpenAI API, sending it a context composed of several messages.
   * It then receives a response that is generated based on this context, aiming to provide a coherent and contextually appropriate continuation or reply.
   *
   * Parameters:
   * - messageList: An array of ChatCompletionMessageParam objects, which include the messages that form the context for the API request.
   *
   * Returns:
   * - A promise that resolves to the generated completion string, which is the API's response based on the provided context.
   */
  async sendCompletion(messageList: ChatCompletionMessageParam[]): Promise<string> {

    const openAICLient = new OpenAI({
      baseURL: AIConfig.ChatConfig.baseURL,
      apiKey: AIConfig.ChatConfig.apiKey,
    });
    const model = AIConfig.ChatConfig.model;


    const MAX_RETRIES = 1;
    let currentTry = 0;
    let lastError: any;

    const params: any = {
      model: model,
      messages: messageList,
      response_format: {
        type: "json_object"
      },
      store: true
    }

    while (currentTry <= MAX_RETRIES) {
      try {
        logger.debug(`[${AIConfig.ChatConfig.provider}->sendCompletion] Attempt ${currentTry + 1}/${MAX_RETRIES + 1}: Sending ${messageList.length} messages.`);

        const reponse: ChatCompletion = await openAICLient.chat.completions.create(params);

        let fullResponse = reponse.choices[0]?.message?.content;

        if (!fullResponse || fullResponse == '') throw new Error(`An error occurred while communicating with ${AIConfig.ChatConfig.provider}. It returned an empty response.`);

        logger.debug(`[${AIConfig.ChatConfig.provider}->sendCompletion] Completion Response:`);
        logger.debug(fullResponse);

        return fullResponse;

      } catch (e: any) {
        lastError = e;
        currentTry++;

        if (currentTry <= MAX_RETRIES) {
          logger.warn(`[${AIConfig.ChatConfig.provider}->sendCompletion] Attempt ${currentTry}/${MAX_RETRIES + 1} failed: ${e.message ?? e}`);
          await new Promise(resolve => setTimeout(resolve, 1000 * currentTry));
        } else {
          logger.error(`[${AIConfig.ChatConfig.provider}->sendCompletion] All ${MAX_RETRIES + 1} attempts failed. Last error: ${e.message ?? e}`);
          throw new Error(`Failed after ${MAX_RETRIES + 1} attempts. Last error: ${e.message ?? e}. Please try again later or consider using an alternative AI. If the issue persists, contact support for further assistance.`);
        }
      }
    }
    throw lastError;
  }

  /**
   * Requests the generation of an image based on a textual description, by interacting with OpenAI's image generation API.
   * This function takes a prompt in the form of text and sends a request to generate an image that corresponds with the text description provided.
   * It aims to utilize OpenAI's capabilities to create visually representative images based on textual inputs.
   *
   * Parameters:
   * - message: A string containing the text description that serves as the prompt for image generation.
   *
   * Returns:
   * - A promise that resolves to the URL of the generated image. This URL points to the image created by OpenAI's API based on the input prompt.
   */
  async createImage(message) {

    logger.debug(`[${AIConfig.ImageConfig.provider}->createImage] Creating message for: "${message}"`);

    const params: any = {
      model: AIConfig.ImageConfig.model,
      prompt: message,
      quality: 'standard',
      n: 1,
      size: "1024x1024",
    }

    const openAICLient = new OpenAI({
      baseURL: AIConfig.ImageConfig.baseURL,
      apiKey: AIConfig.ImageConfig.apiKey,
    });

    const response = await openAICLient.images.generate(params);

    return response.data;
  }

  /**
   * Generates speech audio from provided text by utilizing OpenAI's Text-to-Speech (TTS) API.
   * This function translates text into spoken words in an audio format. It offers a way to convert written messages into audio, providing an audible version of the text content.
   * If a specific voice model is specified in the configuration, the generated speech will use that voice.
   *
   * Parameters:
   * - message: A string containing the text to be converted into speech. This text serves as the input for the TTS engine.
   *
   * Returns:
   * - A promise that resolves to a buffer containing the audio data in MP3 format. This buffer can be played back or sent as an audio message.
   */
  async speech(message, responseFormat?) {

    logger.debug(`[${AIConfig.SpeechConfig.provider}->speech] Creating speech audio for: "${message}"`);

    const openAICLient = new OpenAI({
      baseURL: AIConfig.SpeechConfig.baseURL,
      apiKey: AIConfig.SpeechConfig.apiKey,
    });

    const response: any = await openAICLient.audio.speech.create({
      model: AIConfig.SpeechConfig.model,
      voice: AIConfig.SpeechConfig.voice,
      input: message,
      response_format: responseFormat || 'mp3'
    });

    logger.debug(`[${AIConfig.SpeechConfig.provider}->speech] Audio Creation OK`);

    return Buffer.from(await response.arrayBuffer());
  }

  /**
   * Transcribes audio content into text using OpenAI's transcription capabilities.
   * This function takes an audio file and sends a request to OpenAI's API to generate a textual representation of the spoken words.
   * It leverages the Whisper model for high-quality transcription, converting audio inputs into readable text output.
   *
   * Parameters:
   * - message: A string indicating the audio file path or description for logging purposes. Currently, it is not used in the function's implementation but can be helpful for future extensions or logging clarity.
   *
   * Returns:
   * - A promise that resolves to a string containing the transcribed text. This string is the result of processing the provided audio through OpenAI's transcription model.
   *
   * Throws:
   * - Any errors encountered during the process of reading the audio file or interacting with OpenAI's API will be thrown and should be handled by the caller function.
   */
  async transcription(stream: any) {
    logger.debug(`[${AIConfig.TranscriptionConfig.provider}->transcription] Creating transcription text for audio"`);

    const openAIClient = new OpenAI({
      baseURL: AIConfig.TranscriptionConfig.baseURL,
      apiKey: AIConfig.TranscriptionConfig.apiKey,
    });

    try {
      // Convertir ReadStream a File o Blob
      const file = await toFile(stream, 'audio.ogg', {type: 'audio/ogg'});
      // Enviar el archivo convertido a la API de transcripción
      const response = await openAIClient.audio.transcriptions.create({
        file: file,
        model: AIConfig.TranscriptionConfig.model,
        language: AIConfig.TranscriptionConfig.language
      });
      return response.text;
    } catch (e: any) {
      logger.error(e.message);
      throw e;
    }
  }

}
