import { spawnSync } from "node:child_process";
import { lstatSync, readFileSync, readlinkSync } from "node:fs";
import { join, relative } from "node:path";
import { SEAT_PROVISION_SHEBANG_ENTRYPOINTS } from "./seat-constants.mjs";

const SHEBANG_RE = /^#!\s*\S+/;

function manifestHomeRel(manifest) {
  const raw = String(manifest?.home ?? "");
  if (!raw.startsWith("~/")) throw new Error(`invalid manifest home: ${manifest?.home}`);
  return raw.slice(2);
}

export function seatProvisionBinRepoRel(moduleRoot, name) {
  const repoRoot = join(moduleRoot, "..", "..", "..");
  const abs = join(moduleRoot, "bin", name);
  return relative(repoRoot, abs).split("\\").join("/");
}

export function gitIndexMode(repoRoot, relPath, deps = {}) {
  const spawn = deps.spawnSync ?? spawnSync;
  const ls = spawn("git", ["-C", repoRoot, "ls-files", "-s", "--", relPath], { encoding: "utf8" });
  if (ls.status !== 0 || !String(ls.stdout).trim()) return null;
  return String(ls.stdout).trim().split(/\s+/)[0] ?? null;
}

function isShebangFile(absPath, deps = {}) {
  const read = deps.readFileSync ?? readFileSync;
  try {
    const first = read(absPath, "utf8").split("\n", 1)[0] ?? "";
    return SHEBANG_RE.test(first);
  } catch {
    return false;
  }
}

export function validateSeatProvisionSourceLayout({ repoRoot, moduleRoot, deps = {} }) {
  const lstat = deps.lstatSync ?? lstatSync;
  const errors = [];
  for (const name of SEAT_PROVISION_SHEBANG_ENTRYPOINTS) {
    const rel = seatProvisionBinRepoRel(moduleRoot, name);
    const abs = join(moduleRoot, "bin", name);
    const gitMode = gitIndexMode(repoRoot, rel, deps);
    if (gitMode !== "100755") errors.push(`source:git-mode:${rel}:${gitMode ?? "missing"}`);
    let st;
    try {
      st = lstat(abs);
    } catch {
      errors.push(`source:missing:${rel}`);
      continue;
    }
    if (st.isSymbolicLink()) {
      errors.push(`source:not-regular-file:${rel}`);
      continue;
    }
    if (!st.isFile()) {
      errors.push(`source:not-regular-file:${rel}`);
      continue;
    }
    if (!(st.mode & 0o111)) errors.push(`source:not-executable:${rel}`);
    if (!isShebangFile(abs, deps)) errors.push(`source:not-shebang:${rel}`);
  }
  return errors.length ? { ok: false, errors } : { ok: true, errors: [] };
}

export function validateSeatProvisionDeployedLayout({ home, manifest, deps = {} }) {
  const lstat = deps.lstatSync ?? lstatSync;
  const readlink = deps.readlinkSync ?? readlinkSync;
  const homeRelPath = manifestHomeRel(manifest);
  const errors = [];
  for (const name of SEAT_PROVISION_SHEBANG_ENTRYPOINTS) {
    const rel = join(homeRelPath, "bin", name).replace(/^\//, "");
    const abs = join(home, rel);
    let st;
    try {
      st = lstat(abs);
    } catch {
      errors.push(`deployed:missing:${rel}`);
      continue;
    }
    if (st.isSymbolicLink()) {
      try {
        readlink(abs);
      } catch {
        errors.push(`deployed:broken-symlink:${rel}`);
        continue;
      }
      errors.push(`deployed:shebang-is-symlink:${rel}`);
      continue;
    }
    if (!st.isFile()) {
      errors.push(`deployed:not-regular-file:${rel}`);
      continue;
    }
    if (!(st.mode & 0o111)) errors.push(`deployed:not-executable:${rel}`);
    if (!isShebangFile(abs, deps)) errors.push(`deployed:not-shebang:${rel}`);
  }
  for (const entry of manifest.entries) {
    if (entry.strategy !== "symlink") continue;
    const rel = join(homeRelPath, entry.path).replace(/^\//, "");
    const abs = join(home, rel);
    let st;
    try {
      st = lstat(abs);
    } catch {
      errors.push(`deployed:symlink-entry-missing:${entry.path}`);
      continue;
    }
    if (!st.isSymbolicLink()) {
      errors.push(`deployed:symlink-entry-not-link:${entry.path}`);
      continue;
    }
    try {
      readlink(abs);
    } catch {
      errors.push(`deployed:symlink-entry-broken:${entry.path}`);
    }
  }
  return errors.length ? { ok: false, errors } : { ok: true, errors: [] };
}
