#!/usr/bin/env bun
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { pathToFileURL } from "node:url";

function option(name) {
  const index = process.argv.indexOf(name);
  if (index === -1) return null;
  const value = process.argv[index + 1];
  if (value === undefined || value.length === 0 || value.startsWith("--")) throw new Error(`${name} requires a value`);
  return value;
}

function record(value, path) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`);
  return value;
}

function required(value, path) {
  if (typeof value !== "string" || value.length === 0 || value.includes("\n")) throw new Error(`${path} must be a non-empty single-line string`);
  return value;
}

function fullCommit(value, path) {
  const commit = required(value, path);
  if (!/^[0-9a-f]{40}$/u.test(commit)) throw new Error(`${path} must be a full Git commit SHA`);
  return commit;
}

function safeRelativePath(value, path) {
  const result = required(value, path);
  if (result.startsWith("/") || result.endsWith("/") || !/^[A-Za-z0-9._/-]+$/u.test(result) || result.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
    throw new Error(`${path} must be a safe relative path`);
  }
  return result;
}

function run(command, args, cwd, allowFailure = false) {
  const result = spawnSync(command, args, { cwd, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
  if (result.error !== undefined) throw result.error;
  if (result.status !== 0 && !allowFailure) {
    const detail = `${result.stderr || result.stdout || "command failed"}`.trim().slice(-3000);
    throw new Error(`${command} ${args.join(" ")} failed (${result.status}): ${detail}`);
  }
  return result;
}

function digest(value) {
  return createHash("sha256").update(value).digest("hex");
}

function validateDelivery(value) {
  const delivery = record(value, "gitOpsDelivery");
  if (delivery.enabled !== true) throw new Error("gitOpsDelivery.enabled must be true");
  const application = record(delivery.application, "gitOpsDelivery.application");
  const author = record(delivery.author, "gitOpsDelivery.author");
  const cas = record(delivery.cas, "gitOpsDelivery.cas");
  const maxAttempts = cas.maxAttempts;
  if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 5) throw new Error("gitOpsDelivery.cas.maxAttempts must be an integer from 1 to 5");
  const parsed = {
    enabled: true,
    targetId: required(delivery.targetId, "gitOpsDelivery.targetId"),
    readUrl: required(delivery.readUrl, "gitOpsDelivery.readUrl"),
    writeUrl: required(delivery.writeUrl, "gitOpsDelivery.writeUrl"),
    branch: required(delivery.branch, "gitOpsDelivery.branch"),
    sourceSnapshotPrefix: required(delivery.sourceSnapshotPrefix, "gitOpsDelivery.sourceSnapshotPrefix").replace(/\/+$/u, ""),
    desiredManifestPath: safeRelativePath(delivery.desiredManifestPath, "gitOpsDelivery.desiredManifestPath"),
    bootstrapApplicationPath: safeRelativePath(delivery.bootstrapApplicationPath, "gitOpsDelivery.bootstrapApplicationPath"),
    releaseStatePath: safeRelativePath(delivery.releaseStatePath, "gitOpsDelivery.releaseStatePath"),
    application: {
      name: required(application.name, "gitOpsDelivery.application.name"),
      namespace: required(application.namespace, "gitOpsDelivery.application.namespace"),
      project: required(application.project, "gitOpsDelivery.application.project"),
      repoUrl: required(application.repoUrl, "gitOpsDelivery.application.repoUrl"),
      targetRevision: required(application.targetRevision, "gitOpsDelivery.application.targetRevision"),
      path: safeRelativePath(application.path, "gitOpsDelivery.application.path"),
      destinationNamespace: required(application.destinationNamespace, "gitOpsDelivery.application.destinationNamespace"),
      automated: application.automated === true,
    },
    author: {
      name: required(author.name, "gitOpsDelivery.author.name"),
      email: required(author.email, "gitOpsDelivery.author.email"),
    },
    cas: { maxAttempts },
  };
  if (parsed.application.targetRevision !== parsed.branch) throw new Error("gitOpsDelivery.application.targetRevision must match branch");
  if (parsed.application.repoUrl !== parsed.readUrl) throw new Error("gitOpsDelivery.application.repoUrl must match readUrl");
  return parsed;
}

export function renderPlatformInfraGiteaApplication(deliveryValue) {
  const delivery = validateDelivery(deliveryValue);
  const application = {
    apiVersion: "argoproj.io/v1alpha1",
    kind: "Application",
    metadata: {
      name: delivery.application.name,
      namespace: delivery.application.namespace,
      labels: {
        "app.kubernetes.io/managed-by": "unidesk",
        "app.kubernetes.io/part-of": "platform-infra",
      },
    },
    spec: {
      project: delivery.application.project,
      source: {
        repoURL: delivery.application.repoUrl,
        targetRevision: delivery.application.targetRevision,
        path: delivery.application.path,
      },
      destination: {
        server: "https://kubernetes.default.svc",
        namespace: delivery.application.destinationNamespace,
      },
      syncPolicy: delivery.application.automated ? {
        automated: { prune: true, selfHeal: true },
        syncOptions: ["CreateNamespace=true"],
      } : undefined,
    },
  };
  return `${Bun.YAML.stringify(application).trim()}\n`;
}

function readReleaseState(path, delivery) {
  if (!existsSync(path)) return null;
  const value = record(JSON.parse(readFileSync(path, "utf8")), path);
  if (value.version !== 1 || value.kind !== "PlatformInfraGiteaGitOpsState") throw new Error(`${path} must be a PlatformInfraGiteaGitOpsState v1 record`);
  if (value.targetId !== delivery.targetId) throw new Error(`${path}.targetId must match gitOpsDelivery.targetId`);
  const result = {
    sourceCommit: fullCommit(value.sourceCommit, `${path}.sourceCommit`),
    desiredManifestSha256: required(value.desiredManifestSha256, `${path}.desiredManifestSha256`),
    bootstrapApplicationSha256: required(value.bootstrapApplicationSha256, `${path}.bootstrapApplicationSha256`),
  };
  if (!/^[0-9a-f]{64}$/u.test(result.desiredManifestSha256) || !/^[0-9a-f]{64}$/u.test(result.bootstrapApplicationSha256)) {
    throw new Error(`${path} materialization hashes must be full SHA-256 hex values`);
  }
  return result;
}

function sourceRelation(sourceRoot, delivery, existingCommit, sourceCommit) {
  if (existingCommit === sourceCommit) return "same";
  const refs = [existingCommit, sourceCommit].map((commit, index) => `+${delivery.sourceSnapshotPrefix}/${commit}:refs/unidesk/platform-infra-gitea-source-${index}`);
  const fetchArgs = existsSync(resolve(sourceRoot, ".git", "shallow"))
    ? ["fetch", "--unshallow", "--filter=blob:none", "origin", ...refs]
    : ["fetch", "--filter=blob:none", "origin", ...refs];
  run("git", fetchArgs, sourceRoot);
  if (run("git", ["merge-base", "--is-ancestor", existingCommit, sourceCommit], sourceRoot, true).status === 0) return "forward";
  if (run("git", ["merge-base", "--is-ancestor", sourceCommit, existingCommit], sourceRoot, true).status === 0) return "superseded";
  return "diverged";
}

function checkoutGitOpsBranch(delivery, sourceRoot, worktree) {
  rmSync(worktree, { recursive: true, force: true });
  run("git", ["clone", "--no-checkout", delivery.readUrl, worktree], sourceRoot);
  run("git", ["remote", "set-url", "--push", "origin", delivery.writeUrl], worktree);
  const fetched = run("git", ["fetch", "origin", `+refs/heads/${delivery.branch}:refs/remotes/origin/${delivery.branch}`], worktree, true).status === 0;
  if (fetched) {
    run("git", ["checkout", "-B", delivery.branch, `refs/remotes/origin/${delivery.branch}`], worktree);
    return run("git", ["rev-parse", `refs/remotes/origin/${delivery.branch}`], worktree).stdout.trim();
  }
  run("git", ["checkout", "--orphan", delivery.branch], worktree);
  run("git", ["rm", "-rf", "."], worktree, true);
  return null;
}

function writeDesiredFiles(worktree, delivery, desiredManifest, applicationManifest, state) {
  const entries = [
    [delivery.desiredManifestPath, desiredManifest],
    [delivery.bootstrapApplicationPath, applicationManifest],
    [delivery.releaseStatePath, `${JSON.stringify(state, null, 2)}\n`],
  ];
  for (const [relativePath, content] of entries) {
    const target = resolve(worktree, relativePath);
    if (!target.startsWith(`${worktree}/`)) throw new Error(`${relativePath} escaped the GitOps worktree`);
    mkdirSync(dirname(target), { recursive: true });
    writeFileSync(target, content, { encoding: "utf8", mode: 0o644 });
  }
  run("git", ["add", "--", ...entries.map(([relativePath]) => relativePath)], worktree);
}

export function publishPlatformInfraGiteaGitOps({
  delivery: deliveryValue,
  desiredManifest,
  sourceRoot,
  sourceCommit: sourceCommitValue,
  worktree,
  beforePush = null,
}) {
  const delivery = validateDelivery(deliveryValue);
  const sourceCommit = fullCommit(sourceCommitValue, "sourceCommit");
  const resolvedSourceRoot = resolve(sourceRoot);
  const resolvedWorktree = resolve(worktree);
  const checkedOutCommit = run("git", ["rev-parse", "HEAD"], resolvedSourceRoot).stdout.trim();
  if (checkedOutCommit !== sourceCommit) throw new Error(`source checkout ${checkedOutCommit} does not match requested sourceCommit ${sourceCommit}`);
  if (typeof desiredManifest !== "string" || !desiredManifest.includes("kind: Deployment") || !desiredManifest.includes("kind: PersistentVolumeClaim") || !desiredManifest.includes("kind: ServiceAccount")) {
    throw new Error("desired manifest must contain the durable bridge Deployment, PersistentVolumeClaim and ServiceAccount");
  }
  const applicationManifest = renderPlatformInfraGiteaApplication(delivery);
  const state = {
    version: 1,
    kind: "PlatformInfraGiteaGitOpsState",
    targetId: delivery.targetId,
    sourceCommit,
    desiredManifestSha256: digest(desiredManifest),
    bootstrapApplicationSha256: digest(applicationManifest),
  };
  let lastPushError = "";
  for (let attempt = 1; attempt <= delivery.cas.maxAttempts; attempt += 1) {
    const baseCommit = checkoutGitOpsBranch(delivery, resolvedSourceRoot, resolvedWorktree);
    const statePath = resolve(resolvedWorktree, delivery.releaseStatePath);
    const existingState = readReleaseState(statePath, delivery);
    if (existingState !== null) {
      const relation = sourceRelation(resolvedSourceRoot, delivery, existingState.sourceCommit, sourceCommit);
      if (relation === "same" && (
        existingState.desiredManifestSha256 !== state.desiredManifestSha256
        || existingState.bootstrapApplicationSha256 !== state.bootstrapApplicationSha256
      )) {
        throw new Error(`same source commit ${sourceCommit} has conflicting GitOps materialization state`);
      }
      if (relation === "superseded") {
        return { ok: true, phase: "gitops-publish", status: "superseded", sourceCommit, authoritativeSourceCommit: existingState.sourceCommit, changed: false, pushAttempts: attempt - 1, valuesPrinted: false };
      }
      if (relation === "diverged") throw new Error(`source commit ${sourceCommit} diverges from authoritative GitOps source ${existingState.sourceCommit}`);
    }
    writeDesiredFiles(resolvedWorktree, delivery, desiredManifest, applicationManifest, state);
    run("git", ["config", "user.name", delivery.author.name], resolvedWorktree);
    run("git", ["config", "user.email", delivery.author.email], resolvedWorktree);
    const changed = run("git", ["diff", "--cached", "--quiet"], resolvedWorktree, true).status !== 0;
    if (!changed) {
      return { ok: true, phase: "gitops-publish", status: "idempotent", sourceCommit, gitOpsCommit: baseCommit, changed: false, pushAttempts: attempt - 1, valuesPrinted: false };
    }
    run("git", ["commit", "-m", `platform-infra: deploy gitea bridge ${sourceCommit.slice(0, 12)}`], resolvedWorktree);
    const gitOpsCommit = run("git", ["rev-parse", "HEAD"], resolvedWorktree).stdout.trim();
    if (typeof beforePush === "function") beforePush({ attempt, baseCommit, gitOpsCommit, worktree: resolvedWorktree });
    const lease = baseCommit === null
      ? `--force-with-lease=refs/heads/${delivery.branch}:`
      : `--force-with-lease=refs/heads/${delivery.branch}:${baseCommit}`;
    const push = run("git", ["push", "--porcelain", lease, "origin", `HEAD:refs/heads/${delivery.branch}`], resolvedWorktree, true);
    if (push.status !== 0) {
      lastPushError = `${push.stderr || push.stdout || "CAS push failed"}`.trim().slice(-2000);
      continue;
    }
    const remoteCommit = run("git", ["ls-remote", delivery.writeUrl, `refs/heads/${delivery.branch}`], resolvedWorktree).stdout.trim().split(/\s+/u)[0] ?? "";
    if (remoteCommit !== gitOpsCommit) throw new Error(`post-push GitOps ref proof failed: expected ${gitOpsCommit}, observed ${remoteCommit}`);
    return { ok: true, phase: "gitops-publish", status: "published", sourceCommit, gitOpsCommit, changed: true, pushAttempts: attempt, valuesPrinted: false };
  }
  throw new Error(`GitOps CAS push failed after ${delivery.cas.maxAttempts} attempts: ${lastPushError || "unknown error"}`);
}

async function main() {
  const sourceRoot = resolve(option("--source-root") ?? process.cwd());
  const sourceCommit = fullCommit(option("--source-commit"), "--source-commit");
  const worktree = resolve(option("--worktree") ?? "/workspace/platform-infra-gitea-gitops");
  const moduleUrl = pathToFileURL(resolve(sourceRoot, "scripts", "src", "platform-infra-gitea.ts")).href;
  const pacModuleUrl = pathToFileURL(resolve(sourceRoot, "scripts", "src", "platform-infra-pipelines-as-code.ts")).href;
  const module = await import(moduleUrl);
  const pacModule = await import(pacModuleUrl);
  const delivery = module.readGiteaWebhookGitOpsDelivery();
  const desiredManifest = [
    module.renderGiteaWebhookSyncDesiredManifest(delivery.targetId).trim(),
    pacModule.renderPacRepositoryDesiredFragment(delivery.targetId).trim(),
  ].filter((item) => item.length > 0).join("\n---\n") + "\n";
  const result = publishPlatformInfraGiteaGitOps({ delivery, desiredManifest, sourceRoot, sourceCommit, worktree });
  process.stdout.write(`${JSON.stringify(result)}\n`);
}

if (import.meta.main) await main();
