import { execFile } from "node:child_process";
import { randomUUID } from "node:crypto";
import { lstat, mkdir, readFile, readdir, realpath, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
import { reviewCandidateBranch } from "./runrail-branch-review-core.mjs";
import { LOCAL_AUDIT_RUNNER } from "./veyact-local-audit-runner-core.mjs";

const execFileAsync = promisify(execFile);
const sampleOwnershipSuffix = ".runrail-guided-sample.json";
const retainedOwnedSampleCount = 3;

async function git(repo, args) {
  return execFileAsync("git", ["-C", repo, ...args], {
    timeout: 30_000,
    maxBuffer: 4 * 1024 * 1024,
    windowsHide: true
  });
}

async function commit(repo, message) {
  await git(repo, [
    "-c",
    "user.email=sample@runrail.local",
    "-c",
    "user.name=Runrail Guided Sample",
    "commit",
    "-m",
    message
  ]);
  return String((await git(repo, ["rev-parse", "HEAD"])).stdout).trim();
}

function pathIsInside(parent, child) {
  const relative = path.relative(parent, child);
  return relative !== "" &&
    relative !== ".." &&
    !relative.startsWith(`..${path.sep}`) &&
    !path.isAbsolute(relative);
}

async function ownedSampleRecord(samplesRoot, entryName) {
  if (!entryName.endsWith(sampleOwnershipSuffix)) return null;
  const sampleId = entryName.slice(0, -sampleOwnershipSuffix.length);
  if (!/^sample-[0-9]{13}-[a-f0-9]{8}$/.test(sampleId)) return null;
  const markerPath = path.join(samplesRoot, entryName);
  const repo = path.join(samplesRoot, sampleId);
  let markerInfo;
  let repoInfo;
  try {
    [markerInfo, repoInfo] = await Promise.all([lstat(markerPath), lstat(repo)]);
  } catch {
    return null;
  }
  if (
    markerInfo.isSymbolicLink() ||
    !markerInfo.isFile() ||
    repoInfo.isSymbolicLink() ||
    !repoInfo.isDirectory()
  ) {
    return null;
  }
  let marker;
  try {
    marker = JSON.parse(await readFile(markerPath, "utf8"));
  } catch {
    return null;
  }
  const keys = Object.keys(marker || {}).sort().join(",");
  const createdAt = Date.parse(marker?.createdAt || "");
  if (
    keys !== "artifactType,createdAt,managed,sampleId,schemaVersion" ||
    marker.schemaVersion !== 1 ||
    marker.artifactType !== "runrail-guided-sample-ownership" ||
    marker.managed !== true ||
    marker.sampleId !== sampleId ||
    !Number.isFinite(createdAt) ||
    new Date(createdAt).toISOString() !== marker.createdAt
  ) {
    return null;
  }
  const canonicalRoot = await realpath(samplesRoot);
  const canonicalRepo = await realpath(repo);
  const canonicalMarker = await realpath(markerPath);
  if (!pathIsInside(canonicalRoot, canonicalRepo) || !pathIsInside(canonicalRoot, canonicalMarker)) {
    return null;
  }
  return { sampleId, repo, markerPath, createdAt };
}

export async function pruneRunrailGuidedSamples(
  samplesRoot,
  {
    retain = retainedOwnedSampleCount,
    protectedSampleId = "",
    removePath = rm
  } = {}
) {
  if (!Number.isSafeInteger(retain) || retain < 1 || retain > 20) {
    throw new TypeError("Guided sample retention must be between one and twenty.");
  }
  if (protectedSampleId && !/^sample-[0-9]{13}-[a-f0-9]{8}$/.test(protectedSampleId)) {
    throw new TypeError("Protected guided sample ID is invalid.");
  }
  if (typeof removePath !== "function") {
    throw new TypeError("Guided sample cleanup requires a removal function.");
  }
  let entries;
  try {
    entries = await readdir(samplesRoot);
  } catch (error) {
    if (error?.code === "ENOENT") return { retained: 0, pruned: 0 };
    throw error;
  }
  const owned = (
    await Promise.all(entries.map((entry) => ownedSampleRecord(samplesRoot, entry)))
  )
    .filter(Boolean)
    .sort((left, right) => {
      if (left.sampleId === protectedSampleId) return -1;
      if (right.sampleId === protectedSampleId) return 1;
      return right.createdAt - left.createdAt || right.sampleId.localeCompare(left.sampleId);
    });
  const stale = owned.slice(retain);
  let pruned = 0;
  const cleanupWarnings = [];
  for (const sample of stale) {
    try {
      await removePath(sample.repo, { recursive: true });
      pruned += 1;
    } catch (error) {
      cleanupWarnings.push({
        sampleId: sample.sampleId,
        stage: "repository",
        code: String(error?.code || "cleanup-failed")
      });
      continue;
    }
    try {
      await removePath(sample.markerPath);
    } catch (error) {
      cleanupWarnings.push({
        sampleId: sample.sampleId,
        stage: "ownership-marker",
        code: String(error?.code || "cleanup-failed")
      });
    }
  }
  return {
    retained: owned.length - pruned,
    pruned,
    cleanupDeferred: cleanupWarnings.length > 0,
    cleanupWarnings
  };
}

export async function createRunrailGuidedSample() {
  const samplesRoot = path.join(LOCAL_AUDIT_RUNNER.operatorRoot, "guided-samples");
  const sampleId = `sample-${Date.now()}-${randomUUID().slice(0, 8)}`;
  const repo = path.join(samplesRoot, sampleId);
  const markerPath = path.join(samplesRoot, `${sampleId}${sampleOwnershipSuffix}`);
  await mkdir(samplesRoot, { recursive: true, mode: 0o700 });
  try {
    await mkdir(path.join(repo, "src", "auth"), { recursive: true, mode: 0o700 });
    await mkdir(path.join(repo, "src", "ui"), { recursive: true, mode: 0o700 });
    await mkdir(path.join(repo, "tests"), { recursive: true, mode: 0o700 });
    await mkdir(path.join(repo, "config"), { recursive: true, mode: 0o700 });
    await mkdir(path.join(repo, ".runrail"), { recursive: true, mode: 0o700 });

    await git(repo, ["init"]);
    await git(repo, ["branch", "-M", "main"]);
    await git(repo, ["config", "core.autocrlf", "false"]);
    await writeFile(
      path.join(repo, "src", "auth", "session.py"),
      [
        "def validate_session(token: str) -> bool:",
        '    """Return whether a local session token is present."""',
        "    return bool(token)",
        ""
      ].join("\n"),
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "tests", "test_session.py"),
      [
        "from src.auth.session import validate_session",
        "",
        "",
        "def test_session_requires_a_token():",
        '    assert validate_session("sample-token") is True',
        '    assert validate_session("") is False',
        ""
      ].join("\n"),
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(path.join(repo, "requirements.txt"), "# No runtime dependencies.\n", {
      encoding: "utf8",
      mode: 0o600
    });
    await writeFile(
      path.join(repo, "src", "ui", "session.mjs"),
      "export const sessionLabel = (active) => active ? \"active\" : \"inactive\";\n",
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "config", "session.json"),
      `${JSON.stringify({ minimumTokenLength: 12, prefix: "rr_local_" }, null, 2)}\n`,
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, ".runrail", "review-contract.json"),
      `${JSON.stringify({
        schemaVersion: 1,
        artifactType: "runrail-review-contract",
        budgets: { maxChangedFiles: 20, maxDiffLines: 500 },
        rules: [
          {
            id: "session-boundary",
            pathPrefixes: ["src/auth"],
            severity: "high",
            reviewLane: "security",
            requiredEvidence: ["project-tests", "security-review"],
            companionPathGroups: [["tests"]]
          }
        ]
      }, null, 2)}\n`,
      { encoding: "utf8", mode: 0o600 }
    );
    await git(repo, [
      "add",
      ".runrail/review-contract.json",
      "config/session.json",
      "requirements.txt",
      "src/auth/session.py",
      "src/ui/session.mjs",
      "tests/test_session.py"
    ]);
    const base = await commit(repo, "Create bounded session fixture");

    await git(repo, ["checkout", "-b", "ai/session-hardening"]);
    await writeFile(
      path.join(repo, "src", "auth", "session.py"),
      [
        "def validate_session(token: str) -> bool:",
        '    """Require the bounded local-session token prefix."""',
        '    return token.startswith("rr_local_") and len(token) >= 18',
        ""
      ].join("\n"),
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "tests", "test_session.py"),
      [
        "from src.auth.session import validate_session",
        "",
        "",
        "def test_session_requires_the_local_prefix():",
        '    assert validate_session("rr_local_123456789") is True',
        '    assert validate_session("sample-token") is False',
        '    assert validate_session("") is False',
        ""
      ].join("\n"),
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "requirements.txt"),
      "# Candidate proposes a dependency review.\ncryptography==42.0.8\n",
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "src", "ui", "session.mjs"),
      "export const sessionLabel = (active) => active ? \"verified\" : \"inactive\";\n",
      { encoding: "utf8", mode: 0o600 }
    );
    await writeFile(
      path.join(repo, "config", "session.json"),
      `${JSON.stringify({ minimumTokenLength: 18, prefix: "rr_local_" }, null, 2)}\n`,
      { encoding: "utf8", mode: 0o600 }
    );
    await git(repo, [
      "add",
      "config/session.json",
      "requirements.txt",
      "src/auth/session.py",
      "src/ui/session.mjs",
      "tests/test_session.py"
    ]);
    const candidate = await commit(repo, "Propose bounded session validation");

    const review = await reviewCandidateBranch({
      repo,
      base,
      candidate,
      verifierProfile: "safe-static"
    });
    const createdAt = new Date().toISOString();
    await writeFile(
      markerPath,
      `${JSON.stringify({
        schemaVersion: 1,
        artifactType: "runrail-guided-sample-ownership",
        managed: true,
        sampleId,
        createdAt
      }, null, 2)}\n`,
      { encoding: "utf8", mode: 0o600 }
    );
    const retention = await pruneRunrailGuidedSamples(samplesRoot, { protectedSampleId: sampleId });
    return {
      ...review,
      guidedSample: true,
      sampleId,
      sampleRoot: repo,
      scenario: "candidate-session-hardening",
      base,
      candidate,
      retention: {
        maxOwnedSamples: retainedOwnedSampleCount,
        retainedOwnedSamples: retention.retained,
        prunedOwnedSamples: retention.pruned,
        cleanupDeferred: retention.cleanupDeferred,
        cleanupWarnings: retention.cleanupWarnings
      },
      sourceApplyPerformed: false,
      projectTestsExecuted: false,
      repositoryUpload: false
    };
  } catch (error) {
    await rm(repo, { recursive: true, force: true });
    await rm(markerPath, { force: true });
    throw error;
  }
}
