import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
import FoldToAscii from "fold-to-ascii";

import config from "./config";
import countTokens from "./tokenCounter";
import { request } from "./execution";

/**
 * formats the request to use as a message contend in OpenAI api
 * @param request the request to format
 * @returns the formatted request
 */
export async function formatRequestOrResponse(request: request | InteractionResponse): Promise<string> {
  if (request instanceof DiscordMessage) {
    return formatMessage(request);
  }
  if (request instanceof InteractionResponse) {
    return formatMessage(await request.fetch());
  }
  return formatMessage(await request.fetchReply());
}

/**
 * Formats the message to use as a message content in OpenAI api
 * @param message the message to format
 * @returns the formatted message
 */
export function formatMessage(message: DiscordMessage): string {
  let rvalue: string = message.cleanContent;

  for (const attachment of message.attachments) {
    rvalue += ` [Attachment: ${attachment[1].name}`;
    rvalue += attachment[1].description ? ` - ${attachment[1].description}` : "";
    rvalue += "]";
  }

  for (const embed of message.embeds) {
    rvalue += ` [Embed:`;
    rvalue += embed.title                 ? ` ${embed.title}` : "";
    rvalue += embed.author                ? ` by ${embed.author.name}` : "";
    rvalue += (embed.title || embed.author) && embed.description ? " -": "";
    rvalue += embed.description           ? ` ${embed.description}` : "";

    for (const field of embed.fields) {
      rvalue += ` [${field.name} - ${field.value}]`;
    }
    rvalue += "]";
  }

  for (const sticker of message.stickers.values()) {
    rvalue += ` [Sticker:`;
    rvalue += sticker.name;
    rvalue += sticker.description ? ` - ${sticker.description}` : "";
    rvalue += "]";
  }

  return rvalue.trim();
}

/**
 * Formats name to be accepted in OpenAI api
 * @param name the name to format
 * @returns formatted name
 */
function formatName(name: string): string {
  // replace all characters to ascii equivelant
  return FoldToAscii.foldReplacing(name)
    // White spaces are not allowed
    .replace(" ", "_")
    // replace characters that are not accepted by api with '_'
    .replace(/[^a-zA-Z0-9_-]/g, "_")
    // remove trailing '-_' characters
    .replace(/^[-_]+|[-_]+$/g, "")
    // remove duplicated '-_' character sequences
    .replace(/([-_])([-_])+/g, "$1");
}

/**
 * Gets the name of the author of the message, formatted to be used in OpenAI api
 * @param message the message to get the author name
 * @returns the proper author name of the message
 */
function getAuthorUsername(message: DiscordMessage): string | undefined {
  if (message.author.id === message.client.user.id ) return undefined;
  if (message.member) {
    const name = formatName(message.member.displayName);
    if (name.length >= 3) return name;
  }
  const name = formatName(message.author.username);
  return name;
}

/**
 * Converts the Collection of Discord Messages to array of OpenAI Messages to send
 * @param messages the collection to convert
 * @returns the converted messages
 */
export default function toOpenAIMessages(messages: Collection<string, DiscordMessage>): OpenAIMessage[] {
  const rvalue: OpenAIMessage[] = [];
  let tokenCount = 0;

  messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp);

  for (const message of messages.values()) {
    const content = formatMessage(message);
    // FIXME: tokens are not being counted properly (it's lower than it is) but it's enough for me for now.
    tokenCount += countTokens(content);
    if (tokenCount > config.limits.tokens) break;
    rvalue.push({
      role: message.author.id == message.client.user.id ? "assistant" : "user",
      content: content,
      name: getAuthorUsername(message),
    });
  }

  rvalue.push(...config.systemPrompt().reverse());

  return rvalue.reverse();
}