import config from "../config.ts";
import { getAssetPath, getResultingURL } from "./assetsService.ts";
import { getItems } from "./bufferingService.ts";
import { uuidLockEcecutor } from "../execHelper.ts";
import { exec, execSync } from "node:child_process";
import { v4 as uuidv4 } from "uuid";
import path from "node:path";
import fs from "node:fs";
import { uuidURLMap } from "../state.ts";

const exampleJob = JSON.parse(fs.readFileSync("./example-job.json", "utf8"));

/**
 * Pads a single-digit number with a leading zero.
 * @param {number} num The number to pad.
 * @returns {string} The padded string.
 */
function pad(num: number): string {
  return num < 10 ? "0" + num : "" + num;
}

/**
 * Returns the current timestamp as a string in the format "YYYY-MM-DDTHH:mm:ss.SSSSSS+ZZ:ZZ".
 * * - YYYY: Full four-digit year.
 * - MM: Two-digit month (01-12).
 * - DD: Two-digit day of the month (01-31).
 * - T: Separator character.
 * - HH: Two-digit hour (00-23).
 * - mm: Two-digit minute (00-59).
 * - ss: Two-digit second (00-59).
 * - SSSSSS: Six-digit microsecond representation (milliseconds with trailing zeros).
 * - +ZZ:ZZ: Timezone offset in hours and minutes.
 * * @returns {string} The formatted timestamp string.
 */
function getDateString(): string {
  const date = new Date();

  // Get date and time components
  const year = date.getFullYear();
  const month = pad(date.getMonth() + 1); // getMonth() is 0-indexed
  const day = pad(date.getDate());
  const hours = pad(date.getHours());
  const minutes = pad(date.getMinutes());
  const seconds = pad(date.getSeconds());

  // Get milliseconds and pad to a six-digit microsecond string
  const milliseconds = date.getMilliseconds();
  const microseconds = String(milliseconds).padStart(3, "0") + "000";

  // Calculate timezone offset
  const offset = -date.getTimezoneOffset();
  const offsetHours = pad(Math.floor(Math.abs(offset) / 60));
  const offsetMinutes = pad(Math.abs(offset) % 60);
  const offsetSign = offset >= 0 ? "+" : "-";

  // Assemble the final string
  const timestamp = `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${microseconds}${offsetSign}${offsetHours}:${offsetMinutes}`;

  return timestamp;
}

function getJobName({
  calendarId,
  sourceURL,
  targetURL,
}: {
  calendarId: string;
  sourceURL: string;
  targetURL: string;
}) {
  const uuid = uuidv4();
  uuidURLMap.set(uuid, getResultingURL(sourceURL, targetURL));

  return `${calendarId}...---...${uuid}`;
}

async function queueJobs(
  jobs: {
    calendarId: string;
    sourceURL: string;
    targetURL: string;
  }[]
) {
  // const jobJSONs: (typeof exampleJob & { name: string })[] = [];
  const steps: (typeof exampleJob)["steps"] = [];

  for (const job of jobs) {
    try {
      const jobName = getJobName(job);
      // const jobJSON = exampleJob;
      const newStep = JSON.parse(JSON.stringify(exampleJob.steps[0]));

      console.log({ sourceURL: job.sourceURL, targetURL: job.targetURL });

      newStep.args.target_path = path.resolve(
        await getAssetPath(job.targetURL)
      );
      newStep.args.source_paths[0] = path.resolve(
        await getAssetPath(job.sourceURL)
      );
      newStep.args.output_path = path.join(
        config.outputDir,
        `${jobName}.${config.outputFormat}`
      );

      steps.push(newStep);
    } catch (error) {
      console.error("Error queueing job:", job, error);
    }
  }

  const jobJSON = JSON.parse(JSON.stringify(exampleJob));
  jobJSON.steps = steps;
  jobJSON.date_created = getDateString();
  jobJSON.date_updated = getDateString();

  fs.writeFileSync(
    path.join(config.facefusion.path, ".jobs", "queued", `${uuidv4()}.json`),
    JSON.stringify(jobJSON)
  );
}

// run face swap
async function run() {
  try {
    console.log(`Running face swap jobs`);
    await uuidLockEcecutor(
      `cd ${config.facefusion.path} && date >> log.txt && ${config.facefusion.pythonBin} facefusion.py ${config.facefusion.args} >> log.txt 2>&1`
    );
    console.log(`Face swap jobs completed`);
  } catch (error) {
    console.error("Error running face swap:", error);
  }
}

async function processLoop() {
  while (true) {
    try {
      const items = await getItems(config.maxJobSize);

      if (items.length === 0) {
        await new Promise((resolve) =>
          setTimeout(resolve, config.jobLoopSleep)
        );
        continue;
      }

      await queueJobs(items);

      await run();

      // break;
    } catch (error) {
      console.error("Error processing loop:", error);
    }
  }
}

export async function startProcessing() {
  try {
    processLoop();
  } catch (error) {
    console.error("Error processing loop:", error);
    throw error;
  }
}
