import fs from "node:fs/promises";
import path from "node:path";
import chalk from "chalk";
import { PACKAGE_ROOT } from "../../../../util";
import { type CommandResult, success, silentFailure } from "../../../lib/command-result";

export type GhActionsSyncSeverity = "error" | "warning";

export interface GhActionsSyncError {
  type: "missing" | "modified";
  severity: GhActionsSyncSeverity;
  filePath: string;
  message: string;
}

export interface GhActionsSyncResult {
  exitCode: number;
  errors: GhActionsSyncError[];
  summary: {
    filesChecked: number;
    errorCount: number;
  };
}

const WORKFLOWS_SRC = path.join(PACKAGE_ROOT, "templates", "workflows");
const ACTIONS_SRC = path.join(PACKAGE_ROOT, "templates", "actions");

const WORKFLOWS_DEST_REL = path.join(".github", "workflows");
const ACTIONS_DEST_REL = path.join(".github", "actions");

interface TemplateFile {
  srcAbs: string;
  destRel: string;
}

async function listWorkflowTemplates(): Promise<TemplateFile[]> {
  let entries: string[];
  try {
    entries = await fs.readdir(WORKFLOWS_SRC);
  } catch {
    return [];
  }
  return entries
    .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml"))
    .map((name) => ({
      srcAbs: path.join(WORKFLOWS_SRC, name),
      destRel: path.join(WORKFLOWS_DEST_REL, name),
    }));
}

async function listActionTemplates(): Promise<TemplateFile[]> {
  let entries: { name: string; isDirectory: () => boolean }[];
  try {
    entries = await fs.readdir(ACTIONS_SRC, { withFileTypes: true });
  } catch {
    return [];
  }
  const files: TemplateFile[] = [];
  for (const entry of entries) {
    if (!entry.isDirectory() || !entry.name.startsWith("erp-kit-")) continue;
    const actionDir = path.join(ACTIONS_SRC, entry.name);
    for (const file of await walkDir(actionDir)) {
      const relWithinAction = path.relative(actionDir, file);
      files.push({
        srcAbs: file,
        destRel: path.join(ACTIONS_DEST_REL, entry.name, relWithinAction),
      });
    }
  }
  return files;
}

async function walkDir(dir: string): Promise<string[]> {
  const results: string[] = [];
  const entries = await fs.readdir(dir, { withFileTypes: true });
  for (const entry of entries) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      results.push(...(await walkDir(full)));
    } else if (entry.isFile()) {
      results.push(full);
    }
  }
  return results;
}

async function readFileOrNull(absPath: string): Promise<string | null> {
  try {
    return await fs.readFile(absPath, "utf-8");
  } catch (e) {
    if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
    throw e;
  }
}

export async function collectGhActionsSyncResult(cwd: string): Promise<GhActionsSyncResult> {
  const templates = [...(await listWorkflowTemplates()), ...(await listActionTemplates())];
  const errors: GhActionsSyncError[] = [];

  for (const { srcAbs, destRel } of templates) {
    const destAbs = path.join(cwd, destRel);
    const sourceContent = await fs.readFile(srcAbs, "utf-8");
    const localContent = await readFileOrNull(destAbs);

    if (localContent === null) {
      errors.push({
        type: "missing",
        severity: "error",
        filePath: destRel,
        message: `Framework template not found locally. Run \`pnpm erp-kit update\` to install it.`,
      });
      continue;
    }

    if (localContent !== sourceContent) {
      errors.push({
        type: "modified",
        severity: "error",
        filePath: destRel,
        message: `Framework template content differs from the version shipped with erp-kit. Run \`pnpm erp-kit update\` to refresh, or revert local edits.`,
      });
    }
  }

  return {
    exitCode: errors.length > 0 ? 1 : 0,
    errors,
    summary: {
      filesChecked: templates.length,
      errorCount: errors.length,
    },
  };
}

export function formatGhActionsSyncReport(result: GhActionsSyncResult): string {
  const lines: string[] = [];
  lines.push(chalk.bold("gh-actions sync-check: Checking framework template sync...\n"));

  if (result.errors.length > 0) {
    lines.push(chalk.red.bold("Errors:\n"));
    for (const error of result.errors) {
      lines.push(`  ${chalk.red(error.filePath)}`);
      lines.push(`    ${error.message}`);
      lines.push("");
    }
  } else {
    lines.push(chalk.green("All framework templates are in sync.\n"));
  }

  lines.push(chalk.bold("Summary:"));
  lines.push(`  Files checked: ${result.summary.filesChecked}`);
  lines.push(`  Errors: ${result.summary.errorCount}`);

  if (result.summary.errorCount > 0) {
    lines.push("");
    lines.push(
      chalk.red.bold(`gh-actions sync-check failed with ${result.summary.errorCount} error(s).`),
    );
  } else {
    lines.push("");
    lines.push(chalk.green.bold("gh-actions sync-check passed."));
  }

  return lines.join("\n");
}

export async function runInternalGhActionsSyncCheck(cwd: string): Promise<CommandResult> {
  const result = await collectGhActionsSyncResult(cwd);
  console.log(formatGhActionsSyncReport(result));
  return result.exitCode === 0 ? success() : silentFailure(result.exitCode);
}
