import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";

export const codeQueueSourceSubdir = "src/components/microservices/code-queue/src";
const tsExtensions = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"] as const;

export interface MissingRelativeImport {
  importer: string;
  specifier: string;
  expected: string[];
}

export interface CodeQueueSourceImportPreflightResult {
  ok: boolean;
  guard: "code-queue-hostpath-source-imports";
  root: string;
  sourceRoot: string;
  checkedFiles: number;
  checkedImports: number;
  missing: MissingRelativeImport[];
  degradedReason: "none" | "source-root-missing" | "missing-relative-import-target";
  message: string;
}

function normalizedRelative(from: string, to: string): string {
  return relative(from, to).split("\\").join("/");
}

function walkTsFiles(dir: string): string[] {
  const result: string[] = [];
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git" || entry.name === ".state") continue;
    const path = join(dir, entry.name);
    if (entry.isDirectory()) {
      result.push(...walkTsFiles(path));
      continue;
    }
    if (entry.isFile() && /\.tsx?$/u.test(entry.name)) result.push(path);
  }
  return result.sort();
}

function stripComments(text: string): string {
  return text
    .replace(/\/\*[\s\S]*?\*\//gu, "")
    .replace(/(^|[^:])\/\/.*$/gmu, "$1");
}

function relativeImportSpecifiers(text: string): string[] {
  const cleaned = stripComments(text);
  const specifiers = new Set<string>();
  const patterns = [
    /\b(?:import|export)\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?["'](\.{1,2}\/[^"']+)["']/gu,
    /\bimport\s*\(\s*["'](\.{1,2}\/[^"']+)["']\s*\)/gu,
  ];
  for (const pattern of patterns) {
    for (const match of cleaned.matchAll(pattern)) {
      const specifier = match[1];
      if (specifier !== undefined) specifiers.add(specifier);
    }
  }
  return [...specifiers].sort();
}

function importCandidates(importer: string, specifier: string): string[] {
  const base = resolve(dirname(importer), specifier);
  if (tsExtensions.some((extension) => base.endsWith(extension))) return [base];
  return [
    ...tsExtensions.map((extension) => `${base}${extension}`),
    ...tsExtensions.map((extension) => join(base, `index${extension}`)),
  ];
}

function importTargetExists(candidates: string[]): boolean {
  return candidates.some((candidate) => {
    if (!existsSync(candidate)) return false;
    const stats = statSync(candidate);
    return stats.isFile();
  });
}

export function codeQueueSourceImportPreflight(rootDir: string): CodeQueueSourceImportPreflightResult {
  const root = resolve(rootDir);
  const sourceRoot = resolve(root, codeQueueSourceSubdir);
  if (!existsSync(sourceRoot) || !statSync(sourceRoot).isDirectory()) {
    return {
      ok: false,
      guard: "code-queue-hostpath-source-imports",
      root,
      sourceRoot,
      checkedFiles: 0,
      checkedImports: 0,
      missing: [],
      degradedReason: "source-root-missing",
      message: `Code Queue source root is missing: ${codeQueueSourceSubdir}`,
    };
  }

  const files = walkTsFiles(sourceRoot);
  const missing: MissingRelativeImport[] = [];
  let checkedImports = 0;
  for (const file of files) {
    const text = readFileSync(file, "utf8");
    for (const specifier of relativeImportSpecifiers(text)) {
      checkedImports += 1;
      const candidates = importCandidates(file, specifier);
      if (importTargetExists(candidates)) continue;
      missing.push({
        importer: normalizedRelative(root, file),
        specifier,
        expected: candidates.map((candidate) => normalizedRelative(root, candidate)),
      });
    }
  }

  return {
    ok: missing.length === 0,
    guard: "code-queue-hostpath-source-imports",
    root,
    sourceRoot,
    checkedFiles: files.length,
    checkedImports,
    missing,
    degradedReason: missing.length === 0 ? "none" : "missing-relative-import-target",
    message: missing.length === 0
      ? `Code Queue hostPath source import preflight passed (${files.length} files, ${checkedImports} relative imports).`
      : `Code Queue hostPath source import preflight failed: ${missing.length} relative import target(s) are missing.`,
  };
}

function optionValue(args: string[], name: string): string | null {
  const index = args.indexOf(name);
  if (index === -1) return null;
  const value = args[index + 1];
  if (value === undefined || value.length === 0) throw new Error(`${name} requires a value`);
  return value;
}

export function runCodeQueueSourceGuardCli(args: string[]): CodeQueueSourceImportPreflightResult {
  if (args.includes("--help") || args.includes("-h")) {
    return {
      ok: true,
      guard: "code-queue-hostpath-source-imports",
      root: process.cwd(),
      sourceRoot: resolve(process.cwd(), codeQueueSourceSubdir),
      checkedFiles: 0,
      checkedImports: 0,
      missing: [],
      degradedReason: "none",
      message: "usage: bun scripts/code-queue-source-guard.ts --root <repo-root>",
    };
  }
  const root = optionValue(args, "--root") ?? process.cwd();
  return codeQueueSourceImportPreflight(root);
}

export function emitCodeQueueSourceGuardCli(args: string[]): void {
  const result = runCodeQueueSourceGuardCli(args);
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
  if (!result.ok) process.exitCode = 1;
}
