import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { SEATS_HOME_REL, SEAT_TRUSTED_CONFIG_DIR } from "./seat-constants.mjs";
import { normalizeGitHubOriginHttps } from "./remote-seat-provision.mjs";

const CONTROL_RE = /[\x00-\x1f\x7f]/;
const SAFE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/i;

export function isSafeSeatId(seatId) {
  return Boolean(seatId && SAFE_ID_RE.test(String(seatId)));
}

export function gitPreflight(projectPath, deps = {}) {
  const spawn = deps.spawnSync ?? spawnSync;
  const errors = [];
  const run = (args) => spawn("git", ["-C", projectPath, ...args], { encoding: "utf8" });
  const status = run(["status", "--porcelain"]);
  if (status.status !== 0) {
    errors.push("git-status-failed");
    return { ok: false, errors };
  }
  const lines = String(status.stdout).split("\n").filter(Boolean);
  const dirty = lines.filter((l) => !l.startsWith("??"));
  const untracked = lines.filter((l) => l.startsWith("??"));
  if (dirty.length) errors.push("dirty-worktree");
  if (untracked.length) errors.push("untracked-files");
  const branch = run(["symbolic-ref", "-q", "HEAD"]);
  if (branch.status !== 0) {
    errors.push("detached-head");
    return { ok: false, errors };
  }
  const branchFull = String(branch.stdout).trim();
  const upstream = run(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
  if (upstream.status !== 0 || !String(upstream.stdout).trim()) {
    errors.push("no-upstream");
    return { ok: false, errors };
  }
  const upstreamFull = String(upstream.stdout).trim();
  const head = run(["rev-parse", "HEAD"]);
  const upRev = run(["rev-parse", "@{upstream}"]);
  if (head.status !== 0 || upRev.status !== 0) {
    errors.push("rev-parse-failed");
    return { ok: false, errors };
  }
  if (String(head.stdout).trim() !== String(upRev.stdout).trim()) {
    const behind = run(["rev-list", "--count", "HEAD..@{upstream}"]);
    const ahead = run(["rev-list", "--count", "@{upstream}..HEAD"]);
    if (behind.status === 0 && Number(behind.stdout) > 0) errors.push("behind-upstream");
    if (ahead.status === 0 && Number(ahead.stdout) > 0) errors.push("unpushed-commits");
    if (!errors.includes("behind-upstream") && !errors.includes("unpushed-commits")) {
      errors.push("diverged-from-upstream");
    }
  }
  if (errors.length) return { ok: false, errors };
  return { ok: true, branchFull, upstreamFull };
}

export function deriveSeatIdentity(projectPath, accountSlug, deps = {}) {
  const spawn = deps.spawnSync ?? spawnSync;
  const root = spawn("git", ["-C", projectPath, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
  if (root.status !== 0) return { ok: false, reason: "not-git-repo" };
  const projectRoot = String(root.stdout).trim();
  const remotes = spawn("git", ["-C", projectRoot, "remote"], { encoding: "utf8" });
  if (remotes.status !== 0) return { ok: false, reason: "git-remote-failed" };
  const names = String(remotes.stdout).split("\n").map((s) => s.trim()).filter(Boolean);
  if (names.length !== 1 || names[0] !== "origin") return { ok: false, reason: "origin-required" };
  const urlResult = spawn("git", ["-C", projectRoot, "remote", "get-url", "origin"], { encoding: "utf8" });
  if (urlResult.status !== 0) return { ok: false, reason: "origin-url-missing" };
  const origin = normalizeGitHubOriginHttps(String(urlResult.stdout).trim());
  if (!origin) return { ok: false, reason: "origin-normalize-failed" };
  const branch = spawn("git", ["-C", projectRoot, "symbolic-ref", "-q", "HEAD"], { encoding: "utf8" });
  if (branch.status !== 0) return { ok: false, reason: "detached-head" };
  const branchFull = String(branch.stdout).trim();
  const upstream = spawn("git", ["-C", projectRoot, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"], { encoding: "utf8" });
  if (upstream.status !== 0 || !String(upstream.stdout).trim()) return { ok: false, reason: "no-upstream" };
  const upstreamFull = String(upstream.stdout).trim();
  const slug = String(accountSlug || "").trim();
  if (!slug || CONTROL_RE.test(slug) || !SAFE_ID_RE.test(slug)) return { ok: false, reason: "invalid-account-slug" };
  const identityInput = `${origin}\0${slug}\0${branchFull}`;
  const digest = createHash("sha256").update(identityInput).digest("hex").slice(0, 16);
  const repoPart = origin.replace(/^https:\/\/github\.com\//, "").replace("/", "-");
  const prefix = `${sanitizeIdPart(repoPart)}-${sanitizeIdPart(slug)}`;
  const seatId = `${prefix}-${digest}`;
  if (!SAFE_ID_RE.test(seatId)) return { ok: false, reason: "seat-id-unsafe" };
  return {
    ok: true,
    seatId,
    projectRoot,
    origin,
    accountSlug: slug,
    branchFull,
    upstreamFull,
    identityInput,
  };
}

function sanitizeIdPart(value) {
  return String(value)
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 48) || "repo";
}

export function seatPaths(seatId, home = process.env.HOME || "/home/user") {
  const seatHome = `${home}/${SEATS_HOME_REL}/${seatId}`;
  return {
    seatHome,
    seatJson: `${seatHome}/seat.json`,
    checkout: `${seatHome}/repo`,
    stateDir: `${home}/.local/state/overdeck/seats/${seatId}`,
    trustedConfigDir: `${home}/.local/state/overdeck/seats/${seatId}/${SEAT_TRUSTED_CONFIG_DIR}`,
    tmuxSocket: `${home}/.local/state/overdeck/seats/${seatId}/tmux.sock`,
    unitName: `agent-seat-${seatId}.scope`,
    profileHome: null,
  };
}

export function seatProfileHome(home, accountSlug, seatId) {
  return `${home}/.claudex-accounts/${accountSlug}/${seatId}`;
}

export function parseSeatJson(raw) {
  try {
    const data = JSON.parse(String(raw));
    if (data?.schemaVersion !== 1) return { ok: false, reason: "schema-version" };
    const required = ["origin", "account", "branch", "upstream", "checkout", "host", "model", "modelSource", "proxy"];
    for (const key of required) {
      if (data[key] === undefined || data[key] === null || data[key] === "") return { ok: false, reason: `missing-${key}` };
    }
    if (data.modelSource !== "claudex-seat-launch") return { ok: false, reason: "model-source" };
    if (!data.proxy?.required || data.proxy.ccpCodexModel !== data.model) return { ok: false, reason: "proxy-model" };
    return { ok: true, seat: data };
  } catch {
    return { ok: false, reason: "json-parse" };
  }
}

export function identityMatchesSeat(identity, seat) {
  return (
    seat.origin === identity.origin
    && seat.account === identity.accountSlug
    && seat.branch === identity.branchFull
    && seat.upstream === identity.upstreamFull
  );
}
