import { readdir, stat, unlink, appendFile, mkdir } from "node:fs/promises";
import { writeFileSync, mkdirSync } from "node:fs";
import path from "node:path";

const LOG_DIR_ENV = process.env["LOG_DIR"];
const RETENTION_DAYS = Number(process.env["LOG_RETENTION_DAYS"] ?? "7");
const LOG_FILE_ENV = process.env["LLM_INSPECTOR_LOG_FILE"];

function getUserDataDir(): string {
  if (process.platform === "win32") {
    return (
      process.env["APPDATA"] ?? path.join(process.env["USERPROFILE"] ?? "C:\\", ".llm-inspector")
    );
  }
  return process.env["HOME"] ?? path.join("/tmp");
}

let resolvedLogDir: string | null = null;

export function resolveLogDir(): string {
  if (resolvedLogDir !== null) return resolvedLogDir;

  const base = getUserDataDir();
  if (LOG_DIR_ENV !== undefined) {
    resolvedLogDir = path.isAbsolute(LOG_DIR_ENV) ? LOG_DIR_ENV : path.join(base, LOG_DIR_ENV);
  } else {
    resolvedLogDir = path.join(base, ".llm-inspector", "logs");
  }
  return resolvedLogDir;
}

export function getLogFilePath(): string {
  const date = new Date();
  const yyyy = date.getUTCFullYear();
  const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
  const dd = String(date.getUTCDate()).padStart(2, "0");
  return path.join(resolveLogDir(), `${yyyy}-${mm}-${dd}.jsonl`);
}

function getInspectorLogPath(): string {
  if (LOG_FILE_ENV !== undefined) {
    return LOG_FILE_ENV;
  }
  const base = getUserDataDir();
  return path.join(base, ".llm-inspector", "logs", "inspector.log");
}

export async function initLogger(): Promise<void> {
  const dir = resolveLogDir();
  const retentionMs = RETENTION_DAYS * 24 * 60 * 60 * 1000;
  const cutoff = Date.now() - retentionMs;

  try {
    const entries = await readdir(dir);
    for (const entry of entries) {
      if (!entry.endsWith(".jsonl")) continue;
      const fullPath = path.join(dir, entry);
      try {
        const s = await stat(fullPath);
        if (s.mtimeMs < cutoff) {
          await unlink(fullPath);
        }
      } catch {
        // Skip files that can't be stat - not critical
      }
    }
  } catch (err) {
    // Log directory initialization errors but don't fail startup
    // eslint-disable-next-line no-console
    console.error("[logger] Failed to initialize log directory:", err);
  }
}

// File-based logger for application logs (not log entries)
let loggerInitialized = false;

async function writeAppLog(message: string): Promise<void> {
  // Auto-initialize on first use if not already initialized
  if (!loggerInitialized) {
    loggerInitialized = true;
    void initLogger();
  }

  try {
    const logPath = getInspectorLogPath();
    const logDirPath = path.dirname(logPath);
    await mkdir(logDirPath, { recursive: true });
    const timestamp = new Date().toISOString();
    await appendFile(logPath, `[${timestamp}] ${message}\n`, "utf-8");
  } catch (err) {
    // Log to stderr since file logging failed
    // eslint-disable-next-line no-console
    console.error(`[logger] Failed to write to ${getInspectorLogPath()}:`, err);
  }
}

// Application logger - writes to inspector.log
export const logger = {
  debug(message: string, ...args: unknown[]): void {
    const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
    void writeAppLog(`[DEBUG] ${msg}`);
  },
  info(message: string, ...args: unknown[]): void {
    const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
    void writeAppLog(`[INFO] ${msg}`);
  },
  warn(message: string, ...args: unknown[]): void {
    const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
    void writeAppLog(`[WARN] ${msg}`);
  },
  error(message: string, ...args: unknown[]): void {
    const msg = args.length > 0 ? `${message} ${args.map(String).join(" ")}` : message;
    void writeAppLog(`[ERROR] ${msg}`);
  },
};

// Write buffer for batching async writes
const MAX_BUFFER_SIZE = 1000;
let writeBuffer: string[] = [];
let writeQueue: Promise<void> = Promise.resolve();

function drainBuffer(): string {
  const toWrite = writeBuffer.join("");
  writeBuffer = [];
  return toWrite;
}

async function flushWriteBuffer(): Promise<void> {
  let toWrite: string;
  {
    toWrite = drainBuffer();
  }
  if (toWrite.length === 0) return;

  try {
    const filePath = getLogFilePath();
    await mkdir(path.dirname(filePath), { recursive: true });
    await appendFile(filePath, toWrite, "utf-8");
  } catch (err) {
    // On failure, re-queue the data so it is not lost
    writeBuffer.unshift(toWrite);
    // eslint-disable-next-line no-console
    console.error("[logger] Failed to flush write buffer, re-queued:", err);
  }
}

export function appendLogEntry(entry: Record<string, unknown>): void {
  const line = JSON.stringify(entry) + "\n";
  writeBuffer.push(line);
  if (writeBuffer.length >= MAX_BUFFER_SIZE) {
    writeQueue = writeQueue.then(() => flushWriteBuffer());
  } else if (writeBuffer.length === 1) {
    writeQueue = writeQueue.then(() => flushWriteBuffer());
  }
}

/**
 * Flush all buffered log entries to disk. Call before process exit
 * to avoid losing unflushed data.
 */
export async function flushLogBuffer(): Promise<void> {
  await writeQueue;
  if (writeBuffer.length > 0) {
    await flushWriteBuffer();
  }
}

// Ensure buffered logs are written before the process exits
process.on("exit", () => {
  if (writeBuffer.length > 0) {
    const toWrite = drainBuffer();
    try {
      // Synchronous write required in 'exit' handler (async I/O not allowed)
      const filePath = getLogFilePath();
      mkdirSync(path.dirname(filePath), { recursive: true });
      writeFileSync(filePath, toWrite, "utf-8");
    } catch {
      // Best-effort: nothing we can do in an exit handler
    }
  }
});

for (const signal of ["SIGINT", "SIGTERM"] as const) {
  process.on(signal, () => {
    void flushLogBuffer().then(() => process.exit(0));
  });
}
