import fs from "node:fs/promises";
import path from "node:path";
import { config } from "../config";
import { logger } from "../utils/logger";
import { Mutex } from "./mutex";

export type LoopLogEntry = {
  jobId: string;
  level: "INFO" | "WARN" | "ERROR";
  message: string;
  createdAt: string;
};

const loopLogMutex = new Mutex();

const getLoopLogPath = (jobId: string) =>
  path.join(config.loopLogsDir, `${jobId}.json`);

const getArchivePath = (jobId: string, torrentHash: string | undefined) => {
  const safeTime = new Date().toISOString().replace(/[:.]/g, "-");
  const suffix = torrentHash ? `-${torrentHash}` : "";
  return path.join(config.loopLogsArchiveDir, `${jobId}${suffix}-${safeTime}.json`);
};

export const appendLoopLog = async (entry: LoopLogEntry) => {
  try {
    await loopLogMutex.run(async () => {
      const filePath = getLoopLogPath(entry.jobId);
      let existing: LoopLogEntry[] = [];
      try {
        const content = await fs.readFile(filePath, "utf-8");
        existing = JSON.parse(content) as LoopLogEntry[];
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
          throw error;
        }
      }
      const next = [...existing, entry].slice(-2000);
      await fs.writeFile(filePath, JSON.stringify(next, null, 2), "utf-8");
    });
  } catch (error) {
    logger.error({ error }, "Failed to append loop log");
  }
};

export const readLoopLogs = async (jobId: string) => {
  const filePath = getLoopLogPath(jobId);
  try {
    const content = await fs.readFile(filePath, "utf-8");
    return JSON.parse(content) as LoopLogEntry[];
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
      throw error;
    }
  }

  try {
    const entries = await fs.readdir(config.loopLogsArchiveDir);
    const matches = entries
      .filter((name) => name.startsWith(jobId))
      .map((name) => path.join(config.loopLogsArchiveDir, name));
    if (!matches.length) {
      return [] as LoopLogEntry[];
    }
    const latest = matches.sort().at(-1) as string;
    const content = await fs.readFile(latest, "utf-8");
    return JSON.parse(content) as LoopLogEntry[];
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
      throw error;
    }
  }
  return [] as LoopLogEntry[];
};

export const archiveLoopLogs = async (jobId: string, torrentHash?: string) => {
  const filePath = getLoopLogPath(jobId);
  try {
    await fs.access(filePath);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") {
      return null;
    }
    throw error;
  }
  const target = getArchivePath(jobId, torrentHash);
  await fs.rename(filePath, target);
  return target;
};
