import type Config from '../types/config';
import imageToBase64 from 'background/utils/image-to-base64';
import isGPTModelGreaterOrEqualTo4 from 'background/utils/version-support-images';
import { ChatCompletionMessageParam, ChatCompletionUserMessageParam } from 'openai/resources';
import { parseMoodleQuestion } from './parse-question';
import { MoodleQuestionQuery /* MoodleQuestionType */ } from '../types/question-types';

// The attempt and the cmid allow us to identify a quiz
type History = {
  host: string;
  cmid: string; // The id of the quiz
  attempt: string; // The attempt of the current quiz
  history: ChatCompletionMessageParam[];
};

const INSTRUCTION = `
You are an expert quiz solver.
Please solve the provided question based on its type and provide the correct result.
- For choice questions, output the exact index(es) of the correct answer(s).
- For text/numerical questions, provide the exact wording or number.
- For essay questions, provide a highly detailed and complete response, adapting exactly to the requested 'format' (HTML vs plain text) and building upon any 'initial_text' template if supplied.
`.trim();

const SYSTEM_INSTRUCTION_MESSAGE = {
  role: 'system',
  content: INSTRUCTION
} as const satisfies ChatCompletionMessageParam;

/**
 * Get the content to send to ChatGPT API (it allows to includes images if supported)
 * @param config
 */
async function getContent(
  config: Config,
  questionElement: HTMLElement,
  // We provide the structured JSON if parsed, otherwise fallback to normalized text string
  textContent: string
): Promise<ChatCompletionUserMessageParam['content']> {
  const imagesElements = questionElement.querySelectorAll('img');

  if (
    !config.includeImages ||
    !isGPTModelGreaterOrEqualTo4(config.model) ||
    imagesElements.length === 0
  ) {
    return textContent;
  }

  const contentWithImages: ChatCompletionUserMessageParam['content'] = [];

  const base64Images = Array.from(imagesElements).map(imgEl => imageToBase64(imgEl));
  const base64ImagesResolved = await Promise.allSettled(base64Images);

  for (const result of base64ImagesResolved) {
    if (result.status === 'fulfilled') {
      contentWithImages.push({
        type: 'image_url',
        image_url: { url: result.value }
      });
    } else if (config.logs) {
      console.error(result.reason);
    }
  }

  contentWithImages.push({
    type: 'text',
    text: textContent
  });

  return contentWithImages;
}

/**
 * Create a new history object from the current page
 * @returns
 */
function createNewHistory(): History {
  const urlParams = new URLSearchParams(document.location.search);

  return {
    host: document.location.host,
    cmid: urlParams.get('cmid') ?? '',
    attempt: urlParams.get('attempt') ?? '',
    history: []
  };
}

/**
 * Load the past history from the session storage otherwise return the default history object
 * @returns
 */
function loadPastHistory(): History | null {
  return JSON.parse(sessionStorage.moodleGPTHistory ?? 'null');
}

/**
 * Check if two history are from the same origin
 * @param a
 * @param b
 * @returns
 */
function areHistoryFromSameQuiz(a: History, b: History): boolean {
  const KEYS_TO_COMPARE: (keyof History)[] = ['host', 'cmid', 'attempt'];

  for (const key of KEYS_TO_COMPARE) {
    if (a[key] !== b[key]) return false;
  }

  return true;
}

/**
 * Return the content to send to chatgpt api with history if needed
 * @param config
 * @param questionElement
 * @param question
 * @returns
 */
async function getContentWithHistory(
  config: Config,
  questionElement: HTMLElement,
  question: string
): Promise<{
  messages: [typeof SYSTEM_INSTRUCTION_MESSAGE, ...ChatCompletionMessageParam[]];
  saveResponse?: (response: string) => void;
  query: MoodleQuestionQuery | null;
}> {
  const parsedQuery = parseMoodleQuestion(questionElement, question);
  const textContent = parsedQuery ? JSON.stringify(parsedQuery, null, 2) : question;

  const content = await getContent(config, questionElement, textContent);
  const message: ChatCompletionMessageParam = { role: 'user', content };

  const buildResult = (historyMsg: ChatCompletionMessageParam[]) => {
    const historyObj = { history: historyMsg };
    return {
      messages: [SYSTEM_INSTRUCTION_MESSAGE, ...historyMsg, message] as [
        typeof SYSTEM_INSTRUCTION_MESSAGE,
        ...ChatCompletionMessageParam[]
      ],
      query: parsedQuery,
      saveResponse(response: string) {
        if (config.history) {
          historyObj.history.push(message);
          historyObj.history.push({ role: 'assistant', content: response });
          // Note we probably need the full 'history' object here to stringify it:
          // We will recreate it or reuse the loaded one
          let historyToSave: History;
          const pastHistory: History | null = loadPastHistory();
          const newHistory: History = createNewHistory();
          if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {
            historyToSave = newHistory;
          } else {
            historyToSave = pastHistory;
          }
          historyToSave.history = historyObj.history;
          sessionStorage.moodleGPTHistory = JSON.stringify(historyToSave);
        }
      }
    };
  };

  if (!config.history) {
    return buildResult([]);
  }

  const pastHistory: History | null = loadPastHistory();
  const newHistory: History = createNewHistory();
  if (pastHistory === null || !areHistoryFromSameQuiz(pastHistory, newHistory)) {
    return buildResult(newHistory.history);
  } else {
    return buildResult(pastHistory.history);
  }
}

export default getContentWithHistory;
