import axios from "axios";
import config from "../config.ts";
import { SQSClient, ReceiveMessageCommand } from "@aws-sdk/client-sqs";
import {
  getResultingURL,
  isImageCompleted,
  isCalendarCompleted,
  getAssetPath,
} from "./assetsService.ts";
import {
  buffer,
  detailsMap,
  calendarSQSMap,
  calendarNumProcessed,
  resultURLCalendarIdMap,
  calendarIdCountMap,
} from "../state.ts";
import { enqueUpdateCalendarStatusEspo } from "./espoStatusService.ts";

const sqsClient = new SQSClient({
  region: config.aws.region,
  credentials: {
    accessKeyId: config.aws.accessKeyId,
    secretAccessKey: config.aws.secretAccessKey,
  },
});

function stripQuotes(input: string): string {
  if (input.startsWith('"') && input.endsWith('"')) {
    return input.substring(1, input.length - 1);
  }
  return input;
}

export function reportDone(calendarId: string, url: string) {
  if (!calendarNumProcessed.has(calendarId)) {
    calendarNumProcessed.set(calendarId, new Set());
  }
  calendarNumProcessed.get(calendarId)?.add(url);
}

async function pullMessages(desiredCount: number): Promise<any[]> {
  const command = new ReceiveMessageCommand({
    QueueUrl: config.aws.queueUrl,
    MaxNumberOfMessages: desiredCount,
  });
  const response = await sqsClient.send(command);

  for (const message of response.Messages ?? []) {
    const contents = stripQuotes(message.Body ?? "");
    calendarSQSMap.set(contents, message.ReceiptHandle ?? "");

    if (contents && !calendarNumProcessed.has(contents)) {
      calendarNumProcessed.set(contents, new Set()); // initialize to empty array
    }
  }

  return response.Messages ?? [];
}

async function pullDetails(message: any) {
  const response = await axios.get(
    `https://cam5.pixika.ai/api/v1/CSubmission/${stripQuotes(
      message.Body ?? ""
    )}`,
    {
      headers: { "x-api-key": config.espo.key },
    }
  );
  return {
    calendarId: stripQuotes(message.Body ?? ""),
    details: response.data,
  };
}

async function setCount(details: any) {
  console.log(details.count);

  let count = parseInt(details.count);
  if (isNaN(count)) {
    count = 12;
  }
  calendarIdCountMap.set(details.id, count);
}

export async function getDetails(calendarId: string) {
  // check map
  if (detailsMap.has(calendarId)) {
    return detailsMap.get(calendarId);
  }

  // pullDetails
  const details = await pullDetails({ Body: calendarId });
  detailsMap.set(calendarId, details);
  setCount(details);
  return details;
}

async function pullLoop() {
  // while (buffer.length < config.bufferSize) {
  while (true) {
    await new Promise((resolve) => setTimeout(resolve, config.bufferLoopSleep));

    console.log(`Buffer length: ${buffer.length}`);
    if (buffer.length > config.bufferSize) {
      continue;
    }

    try {
      console.log(`Pulling messages from sqs`);
      const messages = await pullMessages(config.maxBatchPull);

      console.log(`Pulled ${messages.length} messages from sqs`);

      // pull details for each messsage
      const details = (await Promise.allSettled(messages.map(pullDetails)))
        .map((result) => (result.status === "fulfilled" ? result.value : null))
        .filter((detail) => detail != null);

      console.log(`Pulled ${details.length} details`);

      // update details map
      for (const detail of details) {
        detailsMap.set(detail.calendarId, detail.details);
        setCount(detail.details);
      }

      // check if it has already been processed

      // const isCompletedArray = await Promise.allSettled(
      //   details.map((c) => isCalendarCompleted(c.calendarId))
      // );
      const isCompletedArray = Array(details.length).fill({
        status: "fulfilled",
        value: false,
      });

      const pendingCalendarIds: string[] = [];

      for (let i = 0; i < details.length; i++) {
        const isCompletedResult = isCompletedArray[i];

        if (
          isCompletedResult.status === "fulfilled" &&
          isCompletedResult.value === false
        ) {
          pendingCalendarIds.push(details[i].calendarId);
        }
      }

      // remove duplicates from pendingCalendarIds
      const uniquePendingCalendarIds = [...new Set(pendingCalendarIds)];

      // generate combinations of source and target URLs
      const combinations: typeof buffer = [];

      for (const calendarId of uniquePendingCalendarIds) {
        const currentCalendarDetails = await getDetails(calendarId);

        if (currentCalendarDetails.aiFeatureStage === "DONE") continue; // already completed

        enqueUpdateCalendarStatusEspo(calendarId, "IN-PROGRESS");

        const targetURLs: string[] = [];
        const count = calendarIdCountMap.get(calendarId) || 12;
        for (let i = 0; i < count; i++) {
          targetURLs.push(
            // getResultingURL(
            //   currentCalendarDetails.faceImageURL,
            currentCalendarDetails[`ef${i + 1}`]
            // )
          );
        }

        for (const targetURL of targetURLs) {
          combinations.push({
            calendarId,
            sourceURL: currentCalendarDetails.faceImageURL,
            targetURL,
          });
        }
      }

      // check if they have been completed

      console.log(`Checking if images are completed`);

      const isImageCompletedArray = await Promise.allSettled(
        combinations.map((i) => {
          const resultingURL = getResultingURL(i.sourceURL, i.targetURL);

          resultURLCalendarIdMap.set(resultingURL, i.calendarId);

          return isImageCompleted(i.calendarId, resultingURL);
        })
      );

      const pendingImages: typeof buffer = [];

      // console.log(isImageCompletedArray);

      for (let i = 0; i < combinations.length; i++) {
        const result = isImageCompletedArray[i];
        if (result.status === "fulfilled" && !result.value) {
          pendingImages.push(combinations[i]);
        } else {
          reportDone(combinations[i].calendarId, combinations[i].targetURL);
        }
      }

      console.log(`downloading assets`);

      // download all assets
      const allAssets = pendingImages
        .map((i) => [i.sourceURL, i.targetURL])
        .flat();
      const allUniqueAssets = [...new Set(allAssets)];

      const allAssetsPaths = await Promise.all(
        allUniqueAssets.map((i) => getAssetPath(i))
      );

      console.log(`Pushing ${pendingImages.length} images to buffer`);

      buffer.push(...pendingImages);

      // return;
    } catch (error) {
      console.error("Error pulling loop:", error);
    }
  }
}

export async function startBuffering() {
  try {
    pullLoop();
  } catch (error) {
    console.error("Error buffering loop:", error);
    throw error;
  }
}

export async function getItems(count: number) {
  // return count items from buffer
  // if buffer is less than count, return all items
  // if buffer is more than count, return count items
  // if buffer is empty, return empty array
  // remove the returned items from buffer
  const items = buffer.slice(0, count);
  buffer.splice(0, count);
  return items;
}

// // for testing
// startBuffering();

// // simulate n message per second processing
// (async () => {
//   const n = 10;
//   let count = 0;
//   setInterval(() => {
//     if (buffer.pop()) console.log(`processed ${++count} images`);
//   }, 1000);
// })();
