import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { spawnSync } from "node:child_process";

export const DEFAULT_TEMPLATE_OWNER = "say828";

function runCommand(command, args, { cwd = process.cwd(), label = "" } = {}) {
  if (label) {
    console.log(`\n> ${label}`);
  }
  const result = spawnSync(command, args, {
    cwd,
    stdio: "inherit",
  });
  if (result.status !== 0) {
    throw new Error(`Command failed: ${command} ${args.join(" ")}`);
  }
}

function commandExists(command) {
  const result = spawnSync("bash", ["-lc", `command -v ${command}`], {
    stdio: "ignore",
  });
  return result.status === 0;
}

function copyDirectoryContents(sourceRoot, destinationRoot) {
  for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
    if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist") {
      continue;
    }
    fs.cpSync(path.join(sourceRoot, entry.name), path.join(destinationRoot, entry.name), {
      recursive: true,
      force: true,
      filter: (source) => {
        const baseName = path.basename(source);
        return baseName !== ".git" && baseName !== "node_modules" && baseName !== "dist";
      },
    });
  }
}

function fetchPage(url) {
  const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || process.env.AGENTIC_GITHUB_TOKEN;
  return fetch(url, {
    headers: {
      Accept: "application/vnd.github+json",
      "User-Agent": "agentic-dev",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
    },
  });
}

export function parseArgs(argv) {
  const args = [...argv];
  const options = {
    command: "init",
    targetDir: "",
    template: "",
    owner: DEFAULT_TEMPLATE_OWNER,
    force: false,
    yes: false,
    skipBootstrap: false,
  };

  if (args.length > 0 && !args[0].startsWith("-")) {
    options.command = args.shift();
  }

  while (args.length > 0) {
    const token = args.shift();
    switch (token) {
      case "--template":
      case "-t":
        options.template = args.shift() ?? "";
        break;
      case "--owner":
        options.owner = args.shift() ?? DEFAULT_TEMPLATE_OWNER;
        break;
      case "--force":
        options.force = true;
        break;
      case "--yes":
      case "-y":
        options.yes = true;
        break;
      case "--skip-bootstrap":
        options.skipBootstrap = true;
        break;
      case "--help":
      case "-h":
        options.command = "help";
        break;
      default:
        if (!options.targetDir && !token.startsWith("-")) {
          options.targetDir = token;
          break;
        }
        throw new Error(`Unknown argument: ${token}`);
    }
  }

  return options;
}

export function usage() {
  return [
    "Usage:",
    "  agentic-dev",
    "  agentic-dev init <target-dir>",
    "",
    "Examples:",
    "  npx agentic-dev",
    "  npx agentic-dev init my-app",
    "  npx agentic-dev init my-app --template template-web --yes",
    "",
    "Options:",
    "  --template, -t     Template repo name or suffix to use",
    "  --owner            GitHub owner for template-* repos (default: say828)",
    "  --force            Allow scaffolding into a non-empty directory",
    "  --yes, -y          Skip interactive prompts",
    "  --skip-bootstrap   Copy the template but skip install/bootstrap",
    "  --help, -h         Show this help",
  ].join("\n");
}

export function ensureTargetDir(targetDir, { force = false } = {}) {
  if (!targetDir) {
    throw new Error("Missing target directory.");
  }
  const resolved = path.resolve(process.cwd(), targetDir);
  fs.mkdirSync(resolved, { recursive: true });
  const entries = fs
    .readdirSync(resolved, { withFileTypes: true })
    .filter((entry) => entry.name !== ".git");
  if (entries.length > 0 && !force) {
    throw new Error(`Target directory is not empty: ${resolved}`);
  }
  return resolved;
}

export async function fetchTemplateRepos({ owner = DEFAULT_TEMPLATE_OWNER } = {}) {
  const repos = [];
  let page = 1;

  while (true) {
    const response = await fetchPage(
      `https://api.github.com/users/${owner}/repos?per_page=100&page=${page}&sort=updated`,
    );
    if (!response.ok) {
      throw new Error(`Failed to fetch template repos from GitHub: ${response.status}`);
    }
    const payload = await response.json();
    if (!Array.isArray(payload)) {
      throw new Error("Unexpected GitHub response while listing template repos.");
    }
    const publicRepos = payload
      .filter((repo) => !repo.private && typeof repo.name === "string")
      .filter((repo) => repo.name.startsWith("template-"))
      .map((repo) => ({
        name: repo.name,
        description: repo.description ?? "",
        cloneUrl: repo.clone_url,
        htmlUrl: repo.html_url,
        defaultBranch: repo.default_branch || "main",
      }));
    repos.push(...publicRepos);
    if (payload.length < 100) {
      break;
    }
    page += 1;
  }

  repos.sort((a, b) => a.name.localeCompare(b.name));
  return repos;
}

export function resolveTemplateRepo(input, repos) {
  const normalized = (input || "").trim();
  if (!normalized) {
    return null;
  }

  const exact = repos.find((repo) => repo.name === normalized);
  if (exact) {
    return exact;
  }

  const normalizedSuffix = normalized.startsWith("template-") ? normalized.slice(9) : normalized;
  const matches = repos.filter((repo) => repo.name === `template-${normalizedSuffix}`);
  if (matches.length === 1) {
    return matches[0];
  }
  if (matches.length > 1) {
    throw new Error(`Ambiguous template repo: ${normalized}`);
  }
  throw new Error(`Template repo not found: ${normalized}`);
}

export function selectTemplateRepo(choice, repos) {
  const normalized = (choice || "").trim();
  if (!normalized) {
    throw new Error("Missing template selection.");
  }

  if (/^\d+$/.test(normalized)) {
    const index = Number.parseInt(normalized, 10) - 1;
    if (index < 0 || index >= repos.length) {
      throw new Error(`Invalid selection: ${normalized}`);
    }
    return repos[index];
  }

  return resolveTemplateRepo(normalized, repos);
}

function ensurePnpm() {
  if (commandExists("pnpm")) {
    return ["pnpm"];
  }
  if (commandExists("corepack")) {
    runCommand("corepack", ["enable"], { label: "Enabling corepack" });
    return ["corepack", "pnpm"];
  }
  runCommand("npm", ["install", "-g", "pnpm"], { label: "Installing pnpm globally" });
  return ["pnpm"];
}

function maybeCreateEnvFile(destinationRoot) {
  const envExample = path.join(destinationRoot, ".env.example");
  const envFile = path.join(destinationRoot, ".env");
  if (fs.existsSync(envExample) && !fs.existsSync(envFile)) {
    fs.copyFileSync(envExample, envFile);
  }
}

function bootstrapScriptPath(destinationRoot) {
  return path.join(
    destinationRoot,
    "sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh",
  );
}

function detectClientSurfaces(destinationRoot) {
  const clientDir = path.join(destinationRoot, "client");
  if (!fs.existsSync(clientDir)) {
    return [];
  }
  return fs
    .readdirSync(clientDir, { withFileTypes: true })
    .filter((entry) => entry.isDirectory())
    .map((entry) => entry.name)
    .sort();
}

function repoContractPath(destinationRoot) {
  return path.join(
    destinationRoot,
    "sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json",
  );
}

function readJsonFile(filePath) {
  if (!fs.existsSync(filePath)) {
    return null;
  }

  try {
    return JSON.parse(fs.readFileSync(filePath, "utf-8"));
  } catch (error) {
    throw new Error(`Failed to parse JSON file: ${filePath}`);
  }
}

function resolveBootstrapTarget(destinationRoot) {
  const contract = readJsonFile(repoContractPath(destinationRoot));
  const defaultTargetName = contract?.frontend?.default_target;
  const targetConfig =
    typeof defaultTargetName === "string"
      ? contract?.frontend?.targets?.[defaultTargetName]
      : null;

  if (typeof targetConfig?.dir === "string" && targetConfig.dir.length > 0) {
    return {
      name: defaultTargetName,
      dir: targetConfig.dir,
    };
  }

  const clientSurfaces = detectClientSurfaces(destinationRoot);
  if (clientSurfaces.length === 1) {
    return {
      name: clientSurfaces[0],
      dir: path.join("client", clientSurfaces[0]),
    };
  }

  if (clientSurfaces.length > 1) {
    throw new Error(
      "Template repo has multiple client surfaces. Add frontend.default_target to sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json.",
    );
  }

  return null;
}

function validateTemplateRepoContents(destinationRoot, templateRepo) {
  const requiredPaths = [
    ".env.example",
    "pnpm-workspace.yaml",
    "sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh",
  ];

  const missing = requiredPaths.filter(
    (relativePath) => !fs.existsSync(path.join(destinationRoot, relativePath)),
  );
  if (missing.length > 0) {
    throw new Error(
      `Template repo ${templateRepo.name} is missing required files: ${missing.join(", ")}`,
    );
  }
}

function runInstallSteps(destinationRoot, bootstrapTarget) {
  maybeCreateEnvFile(destinationRoot);

  const pnpmCommand = ensurePnpm();
  runCommand(pnpmCommand[0], [...pnpmCommand.slice(1), "install"], {
    cwd: destinationRoot,
    label: "Installing workspace dependencies",
  });

  const executedSteps = ["pnpm install"];

  if (bootstrapTarget) {
    runCommand(
      pnpmCommand[0],
      [
        ...pnpmCommand.slice(1),
        "--dir",
        bootstrapTarget.dir,
        "exec",
        "playwright",
        "install",
        "chromium",
      ],
      {
        cwd: destinationRoot,
        label: `Installing Playwright Chromium for ${bootstrapTarget.name}`,
      },
    );
    executedSteps.push(`pnpm --dir ${bootstrapTarget.dir} exec playwright install chromium`);
  }

  return executedSteps;
}

function runBootstrapStep(destinationRoot, bootstrapTarget) {
  const bootstrapScript = bootstrapScriptPath(destinationRoot);
  if (!fs.existsSync(bootstrapScript)) {
    throw new Error(
      `Missing bootstrap script in template repo: ${path.relative(destinationRoot, bootstrapScript)}`,
    );
  }

  const args = [bootstrapScript, "."];
  if (bootstrapTarget?.name) {
    args.push(bootstrapTarget.name);
  }

  runCommand("bash", args, {
    cwd: destinationRoot,
    label: "Running frontend parity bootstrap",
  });

  return [bootstrapTarget?.name ? `bootstrap_frontend_parity.sh . ${bootstrapTarget.name}` : "bootstrap_frontend_parity.sh ."];
}

export function scaffoldFromTemplateRepo({ destinationRoot, templateRepo }) {
  const cloneRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agentic-dev-template-"));
  const repoRoot = path.join(cloneRoot, templateRepo.name);

  try {
    runCommand("git", ["clone", "--depth", "1", templateRepo.cloneUrl, repoRoot], {
      label: `Cloning ${templateRepo.name}`,
    });
    copyDirectoryContents(repoRoot, destinationRoot);
  } finally {
    fs.rmSync(cloneRoot, { recursive: true, force: true });
  }
}

export function installTemplateRepo({
  destinationRoot,
  templateRepo,
  skipBootstrap = false,
}) {
  scaffoldFromTemplateRepo({ destinationRoot, templateRepo });
  validateTemplateRepoContents(destinationRoot, templateRepo);
  const bootstrapTarget = resolveBootstrapTarget(destinationRoot);

  const executedSteps = runInstallSteps(destinationRoot, bootstrapTarget);
  if (!skipBootstrap) {
    executedSteps.push(...runBootstrapStep(destinationRoot, bootstrapTarget));
  }
  const bootstrapHint = bootstrapTarget
    ? `cd ${bootstrapTarget.dir} && npm run ui:parity:bootstrap`
    : "bash sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh .";
  return {
    destinationRoot,
    templateRepo: templateRepo.name,
    executedSteps,
    nextSteps: skipBootstrap ? [bootstrapHint] : [],
  };
}
