import { spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { repoRoot } from "./config";

interface GitHubPushFallbackOptions {
  repo: string | null;
  branch: string | null;
  cwd: string;
  hostName: string;
  port: number;
  confirm: boolean;
}

export function runGitToolsCommand(args: string[]): Record<string, unknown> {
  const [action] = args;
  if (action === undefined || action === "help" || action === "--help" || action === "-h") return gitToolsHelp();
  if (action !== "github-push-fallback") throw new Error(`unsupported git command: ${action}`);
  const options = parseGitHubPushFallbackOptions(args.slice(1));
  const repo = options.repo ?? deriveGitHubRepo(options.cwd);
  const branch = options.branch ?? currentGitBranch(options.cwd);
  if (repo === null) throw new Error("git github-push-fallback requires --repo owner/name, or an origin remote that points at GitHub");
  if (branch === null) throw new Error("git github-push-fallback requires --branch <branch>, or a checked-out branch");
  validateGitHubRepo(repo);
  validatePushRef(branch);

  const remoteUrl = `ssh://git@ssh.github.com:${options.port}/${repo}.git`;
  const sshCommand = [
    "ssh",
    "-o", `HostName=${options.hostName}`,
    "-o", `Port=${options.port}`,
    "-o", "HostKeyAlias=ssh.github.com",
    "-o", "StrictHostKeyChecking=accept-new",
  ].join(" ");
  const argv = ["git", "push", remoteUrl, branch];
  const base = {
    ok: true,
    command: "git github-push-fallback",
    cwd: options.cwd,
    mutation: options.confirm,
    repository: repo,
    branch,
    remoteUrl,
    ssh: {
      hostName: options.hostName,
      port: options.port,
      hostKeyAlias: "ssh.github.com",
      strictHostKeyChecking: "accept-new",
    },
    env: {
      GIT_SSH_COMMAND: sshCommand,
      valuesPrinted: false,
    },
    argv,
    boundary: "does not edit git remotes; intended for GitHub DNS/port-22 reachability fallback only",
  };

  if (!options.confirm) {
    return {
      ...base,
      executed: false,
      dryRun: true,
      next: [
        "rerun with --confirm to execute this one push without changing origin",
        "use --host-name <ip> only when both github.com and ssh.github.com DNS are broken on the target host",
      ],
    };
  }

  const result = spawnSync("git", ["push", remoteUrl, branch], {
    cwd: options.cwd,
    env: { ...process.env, GIT_SSH_COMMAND: sshCommand },
    encoding: "utf8",
    timeout: 120_000,
  });
  const stdout = result.stdout ?? "";
  const stderr = result.stderr ?? "";
  const ok = result.status === 0;
  return {
    ...base,
    ok,
    executed: true,
    dryRun: false,
    exitCode: result.status,
    signal: result.signal,
    stdoutTail: tail(stdout, 2000),
    stderrTail: tail(stderr, 4000),
    diagnostic: classifyGitPushFailure(`${stdout}\n${stderr}`),
  };
}

function gitToolsHelp(): Record<string, unknown> {
  return {
    ok: true,
    command: "git",
    usage: [
      "bun scripts/cli.ts git github-push-fallback --repo pikasTech/HWLAB --branch fix/name",
      "bun scripts/cli.ts git github-push-fallback --repo pikasTech/HWLAB --branch fix/name --host-name 140.82.116.36 --confirm",
    ],
    behavior: [
      "Plans or executes a one-shot GitHub push through ssh.github.com:443 without editing git remotes.",
      "Default output is a dry-run plan. Add --confirm to execute the push.",
      "Use --host-name <ip> only when target-host DNS cannot resolve GitHub SSH names; HostKeyAlias remains ssh.github.com.",
    ],
    options: {
      "--repo <owner/name>": "GitHub repository. Defaults to parsing origin.",
      "--branch <branch>": "Local branch/ref to push. Defaults to the checked-out branch.",
      "--cwd <path>": "Git worktree. Defaults to the UniDesk repo root.",
      "--host-name <host-or-ip>": "SSH HostName override. Defaults to ssh.github.com.",
      "--port <n>": "SSH port. Defaults to 443.",
      "--confirm": "Execute the push. Without this flag, only prints a plan.",
    },
  };
}

function parseGitHubPushFallbackOptions(args: string[]): GitHubPushFallbackOptions {
  const options: GitHubPushFallbackOptions = {
    repo: null,
    branch: null,
    cwd: repoRoot,
    hostName: "ssh.github.com",
    port: 443,
    confirm: false,
  };
  for (let index = 0; index < args.length; index += 1) {
    const arg = args[index] ?? "";
    const next = args[index + 1];
    if (arg === "--repo") {
      options.repo = requireValue(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--repo=")) {
      options.repo = requireValue("--repo", arg.slice("--repo=".length));
      continue;
    }
    if (arg === "--branch") {
      options.branch = requireValue(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--branch=")) {
      options.branch = requireValue("--branch", arg.slice("--branch=".length));
      continue;
    }
    if (arg === "--cwd") {
      options.cwd = resolve(requireValue(arg, next));
      index += 1;
      continue;
    }
    if (arg.startsWith("--cwd=")) {
      options.cwd = resolve(requireValue("--cwd", arg.slice("--cwd=".length)));
      continue;
    }
    if (arg === "--host-name" || arg === "--host") {
      options.hostName = requireValue(arg, next);
      index += 1;
      continue;
    }
    if (arg.startsWith("--host-name=")) {
      options.hostName = requireValue("--host-name", arg.slice("--host-name=".length));
      continue;
    }
    if (arg === "--host-ip") {
      options.hostName = requireValue(arg, next);
      index += 1;
      continue;
    }
    if (arg === "--port") {
      options.port = parsePort(requireValue(arg, next));
      index += 1;
      continue;
    }
    if (arg.startsWith("--port=")) {
      options.port = parsePort(requireValue("--port", arg.slice("--port=".length)));
      continue;
    }
    if (arg === "--confirm") {
      options.confirm = true;
      continue;
    }
    if (arg === "--dry-run") continue;
    throw new Error(`unsupported git github-push-fallback option: ${arg}`);
  }
  validateHostName(options.hostName);
  return options;
}

function deriveGitHubRepo(cwd: string): string | null {
  const result = gitCapture(cwd, ["remote", "get-url", "origin"]);
  if (!result.ok) return null;
  const remote = result.stdout.trim();
  const patterns = [
    /^git@github\.com:([^/\s]+\/[^/\s]+?)(?:\.git)?$/u,
    /^https:\/\/github\.com\/([^/\s]+\/[^/\s]+?)(?:\.git)?$/u,
    /^ssh:\/\/git@(?:github\.com|ssh\.github\.com)(?::\d+)?\/([^/\s]+\/[^/\s]+?)(?:\.git)?$/u,
  ];
  for (const pattern of patterns) {
    const match = remote.match(pattern);
    if (match?.[1] !== undefined) return match[1];
  }
  return null;
}

function currentGitBranch(cwd: string): string | null {
  const result = gitCapture(cwd, ["branch", "--show-current"]);
  const branch = result.ok ? result.stdout.trim() : "";
  return branch.length > 0 ? branch : null;
}

function gitCapture(cwd: string, args: string[]): { ok: boolean; stdout: string; stderr: string } {
  const result = spawnSync("git", args, { cwd, encoding: "utf8", timeout: 10_000 });
  return {
    ok: result.status === 0,
    stdout: result.stdout ?? "",
    stderr: result.stderr ?? "",
  };
}

function classifyGitPushFailure(output: string): Record<string, unknown> | null {
  if (/Could not resolve hostname github\.com|Temporary failure in name resolution|Name or service not known/iu.test(output)) {
    return {
      kind: "github-dns-or-name-resolution",
      next: "retry with git github-push-fallback; if ssh.github.com DNS also fails, pass --host-name <known GitHub SSH IP>",
    };
  }
  if (/connect to host github\.com port 22|Connection timed out|Network is unreachable/iu.test(output)) {
    return {
      kind: "github-port-22-or-network-blocked",
      next: "retry through ssh.github.com:443 with git github-push-fallback",
    };
  }
  if (/Permission denied \(publickey\)|Authentication failed|Repository not found/iu.test(output)) {
    return {
      kind: "auth-or-repository-access",
      next: "do not keep retrying network fallbacks; inspect SSH key, deploy key, repository name, and GitHub access",
    };
  }
  return null;
}

function requireValue(name: string, value: string | undefined): string {
  if (value === undefined || value.length === 0) throw new Error(`${name} requires a non-empty value`);
  return value;
}

function parsePort(value: string): number {
  const port = Number(value);
  if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error("--port must be an integer from 1 to 65535");
  return port;
}

function validateGitHubRepo(value: string): void {
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(value)) throw new Error("--repo must be owner/name");
}

function validatePushRef(value: string): void {
  if (!/^[A-Za-z0-9_./:-]+$/u.test(value) || value.includes("..") || value.startsWith("-")) throw new Error("--branch must be a simple git ref without whitespace or shell characters");
}

function validateHostName(value: string): void {
  if (!/^[A-Za-z0-9_.:-]+$/u.test(value) || value.startsWith("-")) throw new Error("--host-name must be a hostname or IP literal");
}

function tail(value: string, maxChars: number): string {
  return value.length > maxChars ? value.slice(-maxChars) : value;
}
