import { spawn } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";

type RunnerDisposition = "ready" | "infra-blocked" | "business-failed";

interface FailureContract {
  failureKind: string;
  httpStatus: number;
  runnerDisposition: RunnerDisposition;
  retryable: boolean;
}

const docPath = "docs/reference/auth-broker.md";
const doc = readFileSync(docPath, "utf8");
const rustMainPath = "src/components/microservices/auth-broker/src/main.rs";
const rustCargoPath = "src/components/microservices/auth-broker/Cargo.toml";
const cliAdapterPath = "scripts/src/auth-broker.ts";
const configPath = "config.json";
const deployPath = "deploy.json";
const composePath = "docker-compose.yml";

function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
  if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}

function assertDocContains(text: string): void {
  assertCondition(doc.includes(text), `missing auth broker doc text: ${text}`);
}

function runCli(args: string[], env: Record<string, string | undefined> = {}): Promise<{ status: number | null; stdout: string; stderr: string; json: Record<string, unknown> | null }> {
  const childEnv = { ...process.env, ...env };
  delete childEnv.GH_TOKEN;
  delete childEnv.GITHUB_TOKEN;
  delete childEnv.UNIDESK_AUTH_BROKER_URL;
  delete childEnv.AUTH_BROKER_URL;
  delete childEnv.UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED;
  delete childEnv.AUTH_BROKER_GITHUB_CONFIGURED;
  delete childEnv.UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF;
  delete childEnv.AUTH_BROKER_GITHUB_CREDENTIAL_REF;
  if (env.GH_TOKEN !== undefined) childEnv.GH_TOKEN = env.GH_TOKEN;
  if (env.GITHUB_TOKEN !== undefined) childEnv.GITHUB_TOKEN = env.GITHUB_TOKEN;
  if (env.UNIDESK_AUTH_BROKER_URL !== undefined) childEnv.UNIDESK_AUTH_BROKER_URL = env.UNIDESK_AUTH_BROKER_URL;
  if (env.AUTH_BROKER_URL !== undefined) childEnv.AUTH_BROKER_URL = env.AUTH_BROKER_URL;
  if (env.UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED !== undefined) childEnv.UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED = env.UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED;
  if (env.AUTH_BROKER_GITHUB_CONFIGURED !== undefined) childEnv.AUTH_BROKER_GITHUB_CONFIGURED = env.AUTH_BROKER_GITHUB_CONFIGURED;
  if (env.UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF !== undefined) childEnv.UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF = env.UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF;
  if (env.AUTH_BROKER_GITHUB_CREDENTIAL_REF !== undefined) childEnv.AUTH_BROKER_GITHUB_CREDENTIAL_REF = env.AUTH_BROKER_GITHUB_CREDENTIAL_REF;
  return new Promise((resolve, reject) => {
    const child = spawn("bun", ["scripts/cli.ts", ...args], {
      cwd: process.cwd(),
      env: childEnv,
    });
    const stdoutChunks: Buffer[] = [];
    const stderrChunks: Buffer[] = [];
    child.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
    child.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
    child.on("error", reject);
    child.on("close", (status) => {
      const stdout = Buffer.concat(stdoutChunks).toString("utf8");
      let json: Record<string, unknown> | null = null;
      try {
        json = JSON.parse(stdout) as Record<string, unknown>;
      } catch {
        json = null;
      }
      resolve({ status, stdout, stderr: Buffer.concat(stderrChunks).toString("utf8"), json });
    });
  });
}

function dataOf(response: Record<string, unknown>): Record<string, unknown> {
  assertCondition(typeof response.data === "object" && response.data !== null && !Array.isArray(response.data), "CLI response data should be object", response);
  return response.data as Record<string, unknown>;
}

function asRecord(value: unknown, message: string): Record<string, unknown> {
  assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), message, value);
  return value as Record<string, unknown>;
}

function assertServiceRegistration(value: unknown): void {
  const registration = asRecord(value, "serviceRegistration must be an object");
  const config = asRecord(registration.config, "serviceRegistration.config must be an object");
  assertCondition(config.ok === true, "auth-broker should be registered in config.json", config);
  assertCondition(config.providerId === "main-server", "auth-broker config provider should be main-server", config);
  assertCondition(config.composeService === "auth-broker", "auth-broker compose service should be stable", config);
  assertCondition(config.containerName === "auth-broker-backend", "auth-broker container name should be stable", config);
  assertCondition(config.public === false, "auth-broker must not be public", config);
  const compose = asRecord(registration.compose, "serviceRegistration.compose must be an object");
  assertCondition(compose.servicePresent === true, "auth-broker compose service should exist", compose);
  assertCondition(compose.profileGated === true, "auth-broker compose service should require auth-broker profile", compose);
  assertCondition(compose.publicPortPublished === false, "auth-broker compose service must not publish a public port", compose);
  assertCondition(compose.mutatesDefaultRuntime === false, "auth-broker compose registration must not mutate default runtime", compose);
  const deploy = asRecord(registration.deploy, "serviceRegistration.deploy must be an object");
  assertCondition(deploy.ok === true, "auth-broker should be registered in deploy.json prod and dev", deploy);
  const prod = asRecord(deploy.prod, "serviceRegistration.deploy.prod must be an object");
  const dev = asRecord(deploy.dev, "serviceRegistration.deploy.dev must be an object");
  assertCondition(prod.present === true && dev.present === true, "deploy.json should include auth-broker in prod and dev", deploy);
  const runtimeCredentialRef = asRecord(registration.runtimeCredentialRef, "runtimeCredentialRef must be an object");
  assertCondition(runtimeCredentialRef.valuesRead === false && runtimeCredentialRef.valuesPrinted === false, "runtime credential ref must be presence-only", runtimeCredentialRef);
}

function extractComposeServiceBlock(composeText: string, serviceName: string): string {
  const lines = composeText.split("\n");
  const startLine = lines.findIndex((line) => line === `  ${serviceName}:`);
  if (startLine < 0) return "";
  let endLine = lines.length;
  for (let index = startLine + 1; index < lines.length; index += 1) {
    if (/^  [A-Za-z0-9][A-Za-z0-9_-]*:$/u.test(lines[index] ?? "")) {
      endLine = index;
      break;
    }
  }
  return lines.slice(startLine, endLine).join("\n");
}

const requiredOperations = [
  "github.auth.status",
  "github.issue.list",
  "github.issue.read",
  "github.pr.list",
  "github.pr.read",
  "github.pr.create",
  "github.pr.comment.create",
];

const forbiddenBoundaries = [
  "gh pr merge",
  "arbitrary `gh api`",
  "Docker registry login",
  "deploy commands",
];

const requiredAuditFields = [
  "requestId",
  "observedAt",
  "caller.plane",
  "operation",
  "repo",
  "credentialRef",
  "credentialValuePrinted",
  "runnerDisposition",
  "retryable",
];

const failureContracts: FailureContract[] = [
  { failureKind: "auth-not-configured", httpStatus: 503, runnerDisposition: "infra-blocked", retryable: false },
  { failureKind: "broker-unavailable", httpStatus: 503, runnerDisposition: "infra-blocked", retryable: true },
  { failureKind: "unauthorized-caller", httpStatus: 403, runnerDisposition: "infra-blocked", retryable: false },
  { failureKind: "repo-not-allowed", httpStatus: 403, runnerDisposition: "business-failed", retryable: false },
  { failureKind: "operation-not-allowed", httpStatus: 403, runnerDisposition: "business-failed", retryable: false },
  { failureKind: "dry-run-required", httpStatus: 409, runnerDisposition: "business-failed", retryable: false },
  { failureKind: "validation-failed", httpStatus: 400, runnerDisposition: "business-failed", retryable: false },
  { failureKind: "github-egress-failed", httpStatus: 502, runnerDisposition: "infra-blocked", retryable: true },
  { failureKind: "github-rate-limited", httpStatus: 429, runnerDisposition: "infra-blocked", retryable: true },
  { failureKind: "github-permission-denied", httpStatus: 403, runnerDisposition: "infra-blocked", retryable: false },
  { failureKind: "scope-insufficient", httpStatus: 403, runnerDisposition: "infra-blocked", retryable: false },
  { failureKind: "repo-not-found", httpStatus: 404, runnerDisposition: "business-failed", retryable: false },
  { failureKind: "upstream-invalid-response", httpStatus: 502, runnerDisposition: "infra-blocked", retryable: true },
];

const samplePreflightResponse = {
  ok: true,
  runnerDisposition: "ready" as const,
  failureKind: null,
  degradedReason: null,
  tokenCoverage: {
    ok: true,
    source: "auth-broker",
    scope: "broker-held-github-credential",
    runnerEnvTokenRequired: false,
    valuesPrinted: false,
  },
  prCapabilityContract: {
    targetBranch: "master",
    systemGhBinaryRequiredForWrites: false,
    preflightCreatesPr: false,
    preflightMergesPr: false,
    brokerProxy: {
      ok: true,
      operations: ["github.auth.status", "github.pr.create"],
      writesRemote: false,
    },
  },
};

function walk(value: unknown, path: string[] = []): void {
  if (typeof value === "string") {
    assertCondition(!/gh[pousr]_[A-Za-z0-9_]{20,}/u.test(value), "sample must not contain GitHub token-like values", { path, value });
    assertCondition(!/github_pat_[A-Za-z0-9_]+/u.test(value), "sample must not contain GitHub PAT-like values", { path, value });
    assertCondition(!/^Bearer\s+/iu.test(value), "sample must not contain Authorization header values", { path, value });
    return;
  }
  if (Array.isArray(value)) {
    value.forEach((item, index) => walk(item, [...path, String(index)]));
    return;
  }
  if (typeof value === "object" && value !== null) {
    for (const [key, entry] of Object.entries(value)) {
      assertCondition(!["token", "secret", "authorization", "cookie"].includes(key.toLowerCase()), "sample must not expose secret-bearing keys", { path, key });
      walk(entry, [...path, key]);
    }
  }
}

async function main(): Promise<void> {
  for (const heading of ["## Existing Paths", "## API", "## Permission Boundary", "## Audit Fields", "## Failure Semantics", "## D601 Dev Acceptance"]) {
    assertDocContains(heading);
  }
  assertDocContains("## P1 Source Registration");
  for (const path of [
    "scripts/src/gh.ts",
    "scripts/src/auth-broker.ts",
    "src/components/microservices/auth-broker/Cargo.toml",
    "src/components/microservices/auth-broker/src/main.rs",
    "config.json",
    "deploy.json",
    "docker-compose.yml",
    "scripts/code-queue-pr-preflight-example.ts",
    "src/components/microservices/code-queue/src/runtime-preflight.ts",
    "scripts/src/code-queue.ts",
    "src/components/microservices/code-queue/src/index.ts",
    "src/components/microservices/code-queue/docker-compose.d601.yml",
    "src/components/microservices/code-queue/Dockerfile",
  ]) {
    assertDocContains(path);
  }
  for (const operation of requiredOperations) assertDocContains(operation);
  for (const boundary of forbiddenBoundaries) assertDocContains(boundary);
  for (const field of requiredAuditFields) assertDocContains(field);
  for (const failure of failureContracts) {
    assertDocContains(failure.failureKind);
    assertCondition(Number.isInteger(failure.httpStatus) && failure.httpStatus >= 400, "failure HTTP status must be an error status", failure);
  }
  assertCondition(new Set(failureContracts.map((item) => item.failureKind)).size === failureContracts.length, "failure kinds must be unique", failureContracts);
  assertCondition(samplePreflightResponse.tokenCoverage.source === "auth-broker", "preflight should use broker token coverage", samplePreflightResponse.tokenCoverage);
  assertCondition(samplePreflightResponse.tokenCoverage.runnerEnvTokenRequired === false, "runner env token must not be required", samplePreflightResponse.tokenCoverage);
  assertCondition(samplePreflightResponse.prCapabilityContract.brokerProxy.writesRemote === false, "P0 preflight must not write remotely", samplePreflightResponse.prCapabilityContract);
  assertCondition(samplePreflightResponse.prCapabilityContract.preflightMergesPr === false, "P0 preflight must not merge PRs", samplePreflightResponse.prCapabilityContract);
  walk(samplePreflightResponse);

  for (const path of [rustCargoPath, rustMainPath, cliAdapterPath, configPath, deployPath, composePath]) {
    assertCondition(existsSync(path), `required auth broker implementation file is missing: ${path}`);
  }
  const rustMain = readFileSync(rustMainPath, "utf8");
  const cliAdapter = readFileSync(cliAdapterPath, "utf8");
  const config = JSON.parse(readFileSync(configPath, "utf8")) as { microservices?: Array<Record<string, unknown>> };
  const deploy = JSON.parse(readFileSync(deployPath, "utf8")) as { environments?: Record<string, { services?: Array<Record<string, unknown>> }> };
  const composeText = readFileSync(composePath, "utf8");
  const authBrokerComposeBlock = extractComposeServiceBlock(composeText, "auth-broker");
  assertCondition(rustMain.includes("GET") && rustMain.includes("/health"), "Rust skeleton should expose GET /health", rustMainPath);
  assertCondition(rustMain.includes("/v1/github/gh"), "Rust skeleton should expose credential-request endpoint", rustMainPath);
  assertCondition(rustMain.includes("/v1/github/pr-preflight"), "Rust skeleton should expose pr-preflight endpoint", rustMainPath);
  assertCondition(rustMain.includes("--healthcheck"), "Rust skeleton should expose a process healthcheck mode", rustMainPath);
  assertCondition(rustMain.includes("credential_value_printed: false"), "audit event must force credentialValuePrinted=false", rustMainPath);
  assertCondition(!rustMain.includes("GH_TOKEN") && !rustMain.includes("GITHUB_TOKEN"), "Rust skeleton must not read runner token env keys", rustMainPath);
  assertCondition(cliAdapter.includes("valuesRead: false") && cliAdapter.includes("valuesPrinted: false"), "CLI adapter must declare secret values unread/unprinted", cliAdapterPath);
  assertCondition(cliAdapter.includes("broker-needed") && cliAdapter.includes("auth-missing"), "CLI adapter must expose broker-needed/auth-missing shape", cliAdapterPath);
  const configService = config.microservices?.find((item) => item.id === "auth-broker");
  assertCondition(configService !== undefined, "config.json should register auth-broker microservice", configPath);
  assertCondition(asRecord(configService?.backend, "auth-broker backend should be object").public === false, "auth-broker backend must be private", configService);
  assertCondition(deploy.environments?.prod?.services?.some((item) => item.id === "auth-broker") === true, "deploy.json prod should include auth-broker", deployPath);
  assertCondition(deploy.environments?.dev?.services?.some((item) => item.id === "auth-broker") === true, "deploy.json dev should include auth-broker", deployPath);
  assertCondition(authBrokerComposeBlock.includes("  auth-broker:") && authBrokerComposeBlock.includes("profiles:") && authBrokerComposeBlock.includes("- auth-broker"), "docker-compose should include auth-broker behind an explicit profile", authBrokerComposeBlock);
  assertCondition(!/^\s{4}ports:/mu.test(authBrokerComposeBlock), "auth-broker compose service must not publish ports", authBrokerComposeBlock);

  const noToken = await runCli(["auth-broker", "pr-preflight", "--repo", "pikasTech/unidesk", "--base", "master", "--head", "feature/auth-broker", "--issue", "59", "--dry-run"]);
  assertCondition(noToken.status === 1, "missing broker endpoint should exit 1", { status: noToken.status, stdout: noToken.stdout, stderr: noToken.stderr });
  assertCondition(noToken.json?.ok === false, "missing token response envelope should fail", noToken.json);
  const noTokenData = dataOf(noToken.json ?? {});
  assertCondition(noTokenData.failureKind === "auth-missing", "missing token should classify as auth-missing", noTokenData);
  assertCondition(noTokenData.degradedReason === "broker-needed", "missing token should classify as broker-needed", noTokenData);
  assertCondition(noTokenData.brokerNeeded === true, "missing token should set brokerNeeded", noTokenData);
  const noTokenAuthBroker = noTokenData.authBroker as Record<string, unknown>;
  assertCondition(noTokenAuthBroker.source === "broker/auth-broker-needed", "missing broker endpoint should expose broker-needed auth source", noTokenAuthBroker);
  assertCondition(noTokenAuthBroker.capability === "missing-token", "missing broker endpoint should expose missing-token capability", noTokenAuthBroker);
  assertCondition(noTokenAuthBroker.nextAction === "configure-auth-broker", "missing broker endpoint should expose next action", noTokenAuthBroker);
  assertServiceRegistration(noTokenData.serviceRegistration);
  assertCondition(!noToken.stdout.includes("contract-secret-marker"), "missing-token response must not leak secret marker strings", noToken.stdout);

  const brokerReady = await runCli([
    "auth-broker",
    "pr-preflight",
    "--repo",
    "pikasTech/unidesk",
    "--base",
    "master",
    "--head",
    "feature/auth-broker",
    "--issue",
    "59",
    "--dry-run",
    "--endpoint",
    "http://user:pass@127.0.0.1:4291?credential=abc",
  ]);
  assertCondition(brokerReady.status === 0, "configured broker dry-run should exit 0", { status: brokerReady.status, stdout: brokerReady.stdout, stderr: brokerReady.stderr });
  assertCondition(brokerReady.json?.ok === true, "configured broker dry-run envelope should succeed", brokerReady.json);
  const readyData = dataOf(brokerReady.json ?? {});
  const tokenCoverage = readyData.tokenCoverage as Record<string, unknown>;
  const brokerCoverage = readyData.brokerCoverage as Record<string, unknown>;
  const readyAuthBroker = readyData.authBroker as Record<string, unknown>;
  const prCapability = readyData.prCapabilityContract as Record<string, unknown>;
  const brokerProxy = prCapability.brokerProxy as Record<string, unknown>;
  assertServiceRegistration(readyData.serviceRegistration);
  assertCondition(readyAuthBroker.source === "auth-broker", "ready auth broker source should be auth-broker", readyAuthBroker);
  assertCondition(readyAuthBroker.capability === "broker-issued-token", "ready auth broker capability should be broker-issued-token", readyAuthBroker);
  assertCondition(readyAuthBroker.nextAction === "use-auth-broker", "ready auth broker next action should use broker", readyAuthBroker);
  assertCondition(tokenCoverage.source === "auth-broker", "ready token coverage should come from broker", tokenCoverage);
  assertCondition(tokenCoverage.runnerEnvTokenRequired === false, "ready token coverage should not require runner env token", tokenCoverage);
  assertCondition(tokenCoverage.valuesPrinted === false, "ready token coverage must not print values", tokenCoverage);
  assertCondition(String(brokerCoverage.endpoint).includes("http://***:***@127.0.0.1:4291/?..."), "endpoint should be sanitized", brokerCoverage);
  assertCondition(prCapability.targetBranch === "master", "P0 capability should preserve target branch", prCapability);
  assertCondition(prCapability.authSource === "broker-issued-token", "P0 capability should expose broker-issued-token auth source", prCapability);
  assertCondition(prCapability.realPrCreateRequiresCommanderAuthorization === true, "real PR create should require commander authorization", prCapability);
  assertCondition(prCapability.preflightCreatesPr === false && prCapability.preflightMergesPr === false, "P0 PR preflight must not write or merge", prCapability);
  assertCondition(brokerProxy.writesRemote === false, "P0 broker proxy should not write remote", brokerProxy);
  assertCondition(Array.isArray(brokerProxy.operations) && brokerProxy.operations.includes("github.pr.create"), "P0 broker proxy should include PR create dry-run operation", brokerProxy);
  walk(readyData);

  const credentialRefPresence = await runCli(
    ["auth-broker", "health", "--dry-run", "--endpoint", "http://127.0.0.1:4291"],
    {
      UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED: "true",
      UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF: "github:contract-secret-marker",
    },
  );
  assertCondition(credentialRefPresence.status === 0, "configured credential-ref presence dry-run should exit 0", credentialRefPresence);
  const credentialRefData = dataOf(credentialRefPresence.json ?? {});
  const registration = asRecord(credentialRefData.serviceRegistration, "credential-ref health should include service registration");
  const runtimeCredentialRef = asRecord(registration.runtimeCredentialRef, "health registration should include runtimeCredentialRef");
  const credentialRef = asRecord(runtimeCredentialRef.credentialRef, "runtimeCredentialRef.credentialRef should be object");
  assertCondition(runtimeCredentialRef.ok === true, "credential ref presence should be ready when configured flag and ref key are present", runtimeCredentialRef);
  assertCondition(credentialRef.valuePreview === "github:<ref>", "credential ref preview should be sanitized", credentialRef);
  assertCondition(!credentialRefPresence.stdout.includes("contract-secret-marker"), "credential ref dry-run must not print the raw credential ref value", credentialRefPresence.stdout);
  walk(credentialRefData);

  process.stdout.write(`${JSON.stringify({
    ok: true,
    docPath,
    implementation: {
      rustMainPath,
      rustCargoPath,
      cliAdapterPath,
      configPath,
      deployPath,
      composePath,
    },
    operations: requiredOperations,
    failureKinds: failureContracts.map((item) => item.failureKind),
    p0Safety: {
      runnerEnvTokenRequired: false,
      preflightWritesRemote: false,
      secretValuesPrinted: false,
      liveWritesDefault: "dry-run-required",
    },
  }, null, 2)}\n`);
}

main().catch((error) => {
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
  process.exitCode = 1;
});
