import { readFileSync } from "node:fs";
import { type UniDeskConfig, readConfig, rootPath } from "./config";

const DEFAULT_REPO = "pikasTech/unidesk";
const DEFAULT_BASE = "master";
const SECRET_ENV_KEYS = ["GH_TOKEN", "GITHUB_TOKEN"] as const;
const BROKER_URL_ENV_KEYS = ["UNIDESK_AUTH_BROKER_URL", "AUTH_BROKER_URL"] as const;
const BROKER_CREDENTIAL_REF_ENV_KEYS = ["UNIDESK_AUTH_BROKER_GITHUB_CREDENTIAL_REF", "AUTH_BROKER_GITHUB_CREDENTIAL_REF"] as const;
const BROKER_CONFIGURED_ENV_KEYS = ["UNIDESK_AUTH_BROKER_GITHUB_CONFIGURED", "AUTH_BROKER_GITHUB_CONFIGURED"] as const;
const AUTH_BROKER_SERVICE_ID = "auth-broker";
const DEFAULT_CAPABILITIES = [
  "github.auth.status",
  "github.issue.list",
  "github.issue.read",
  "github.pr.list",
  "github.pr.read",
  "github.pr.create",
  "github.pr.comment.create",
  "github.pr.preflight.dry-run",
] as const;

type BrokerCommand = "contract" | "credential-request" | "pr-preflight" | "health";
type RunnerDisposition = "ready" | "infra-blocked" | "business-failed";
type ConfigSource = "config.json" | "unavailable";

interface BrokerAdapterOptions {
  command: BrokerCommand;
  repo: string;
  operation: string;
  dryRun: boolean;
  endpoint: string | null;
  base: string;
  head: string;
  issueNumber: number | null;
}

interface AuthBrokerServiceRegistration {
  ok: boolean;
  serviceId: "auth-broker";
  source: ConfigSource;
  configured: boolean;
  providerId: string | null;
  deploymentMode: string | null;
  proxyMode: string | null;
  backendBaseUrl: string | null;
  healthPath: string | null;
  allowedPathPrefixes: string[];
  composeService: string | null;
  containerName: string | null;
  dockerfile: string | null;
  public: boolean | null;
  error: string | null;
}

interface ComposeProfileRegistration {
  ok: boolean;
  source: "docker-compose.yml";
  serviceId: "auth-broker";
  servicePresent: boolean;
  profileGated: boolean;
  profiles: string[];
  restart: string | null;
  publicPortPublished: boolean;
  exposesPort: boolean;
  mutatesDefaultRuntime: false;
}

interface DeployManifestRegistration {
  ok: boolean;
  source: "deploy.json";
  serviceId: "auth-broker";
  prod: { present: boolean; commitId: string | null };
  dev: { present: boolean; commitId: string | null };
  dryRunOnly: true;
}

interface RuntimeCredentialRefPresence {
  ok: boolean;
  source: string | null;
  configuredFlag: {
    present: boolean;
    key: string | null;
    truthy: boolean;
  };
  credentialRef: {
    present: boolean;
    key: string | null;
    valuePrinted: false;
    valuePreview: string | null;
  };
  presenceOnly: true;
  valuesRead: false;
  valuesPrinted: false;
}

function hasEnvKey(name: string): boolean {
  return Object.prototype.hasOwnProperty.call(process.env, name);
}

function firstPresentEnvKey(keys: readonly string[]): string | null {
  for (const key of keys) {
    if (hasEnvKey(key)) return key;
  }
  return null;
}

function stringOption(args: string[], name: string): string | undefined {
  const index = args.indexOf(name);
  if (index === -1) return undefined;
  const value = args[index + 1];
  if (value === undefined || value.length === 0) throw new Error(`${name} requires a non-empty value`);
  return value;
}

function sanitizeEndpoint(value: string): string {
  try {
    const parsed = new URL(value);
    if (parsed.username.length > 0) parsed.username = "***";
    if (parsed.password.length > 0) parsed.password = "***";
    if (parsed.search.length > 0) parsed.search = "?...";
    parsed.hash = "";
    return parsed.toString();
  } catch {
    return value.includes("?") ? `${value.split("?")[0]}?...` : value;
  }
}

function sanitizeCredentialRef(value: string): string {
  const trimmed = value.trim();
  if (trimmed.length === 0) return "<empty>";
  const separator = trimmed.indexOf(":");
  if (separator <= 0) return "<credential-ref>";
  return `${trimmed.slice(0, separator)}:<ref>`;
}

function numberOption(args: string[], name: string): number | null {
  const raw = stringOption(args, name);
  if (raw === undefined) return null;
  const value = Number(raw);
  if (!Number.isInteger(value) || value <= 0) throw new Error(`${name} must be a positive integer`);
  return value;
}

function firstConfiguredBrokerUrl(): string | null {
  for (const key of BROKER_URL_ENV_KEYS) {
    if (hasEnvKey(key)) return `<${key}>`;
  }
  return null;
}

function envTruthy(key: string | null): boolean {
  if (key === null) return false;
  const value = process.env[key]?.trim().toLowerCase();
  return value === "1" || value === "true" || value === "yes" || value === "on";
}

function runtimeCredentialRefPresence(): RuntimeCredentialRefPresence {
  const configuredKey = firstPresentEnvKey(BROKER_CONFIGURED_ENV_KEYS);
  const refKey = firstPresentEnvKey(BROKER_CREDENTIAL_REF_ENV_KEYS);
  return {
    ok: configuredKey !== null && envTruthy(configuredKey) && refKey !== null,
    source: refKey === null ? null : "broker-held-github-credential-ref",
    configuredFlag: {
      present: configuredKey !== null,
      key: configuredKey,
      truthy: envTruthy(configuredKey),
    },
    credentialRef: {
      present: refKey !== null,
      key: refKey,
      valuePrinted: false,
      valuePreview: refKey === null ? null : sanitizeCredentialRef(process.env[refKey] ?? ""),
    },
    presenceOnly: true,
    valuesRead: false,
    valuesPrinted: false,
  };
}

function readConfigRegistration(): AuthBrokerServiceRegistration {
  let config: UniDeskConfig;
  try {
    config = readConfig();
  } catch (error) {
    return {
      ok: false,
      serviceId: AUTH_BROKER_SERVICE_ID,
      source: "unavailable",
      configured: false,
      providerId: null,
      deploymentMode: null,
      proxyMode: null,
      backendBaseUrl: null,
      healthPath: null,
      allowedPathPrefixes: [],
      composeService: null,
      containerName: null,
      dockerfile: null,
      public: null,
      error: error instanceof Error ? error.message : String(error),
    };
  }
  const service = config.microservices.find((item) => item.id === AUTH_BROKER_SERVICE_ID);
  return {
    ok: service !== undefined,
    serviceId: AUTH_BROKER_SERVICE_ID,
    source: "config.json",
    configured: service !== undefined,
    providerId: service?.providerId ?? null,
    deploymentMode: service?.deployment.mode ?? null,
    proxyMode: service?.backend.proxyMode ?? null,
    backendBaseUrl: service?.backend.nodeBaseUrl ?? null,
    healthPath: service?.backend.healthPath ?? null,
    allowedPathPrefixes: service?.backend.allowedPathPrefixes ?? [],
    composeService: service?.repository.composeService ?? null,
    containerName: service?.repository.containerName ?? null,
    dockerfile: service?.repository.dockerfile ?? null,
    public: service?.backend.public ?? null,
    error: service === undefined ? "auth-broker is not registered in config.json microservices" : null,
  };
}

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");
}

function composeProfileRegistration(): ComposeProfileRegistration {
  const composeText = readFileSync(rootPath("docker-compose.yml"), "utf8");
  const block = extractComposeServiceBlock(composeText, AUTH_BROKER_SERVICE_ID);
  const blockLines = block.split("\n");
  const profiles: string[] = [];
  const profileStart = blockLines.findIndex((line) => /^\s{4}profiles:\s*$/u.test(line));
  if (profileStart >= 0) {
    for (let index = profileStart + 1; index < blockLines.length; index += 1) {
      const line = blockLines[index] ?? "";
      if (/^\s{4}[A-Za-z0-9_-]+:/u.test(line)) break;
      const profile = line.match(/^\s{6}-\s+"?([A-Za-z0-9_.-]+)"?\s*$/u)?.[1];
      if (profile !== undefined) profiles.push(profile);
    }
  }
  const restart = block.match(/^\s{4}restart:\s+"?([^"\n]+)"?\s*$/mu)?.[1] ?? null;
  const portsSection = /^\s{4}ports:\s*$/mu.test(block);
  const portMapping = /^\s{6}-\s+["']?[^"'\n]*4291:/mu.test(block);
  return {
    ok: block.length > 0 && profiles.includes(AUTH_BROKER_SERVICE_ID) && !portsSection && !portMapping,
    source: "docker-compose.yml",
    serviceId: AUTH_BROKER_SERVICE_ID,
    servicePresent: block.length > 0,
    profileGated: profiles.includes(AUTH_BROKER_SERVICE_ID),
    profiles,
    restart,
    publicPortPublished: portsSection || portMapping,
    exposesPort: block.includes('"4291"'),
    mutatesDefaultRuntime: false,
  };
}

function serviceCommitFromDeployJson(environment: "prod" | "dev"): string | null {
  const parsed = JSON.parse(readFileSync(rootPath("deploy.json"), "utf8")) as {
    environments?: Record<string, { services?: Array<{ id?: unknown; commitId?: unknown }> }>;
  };
  const service = parsed.environments?.[environment]?.services?.find((item) => item.id === AUTH_BROKER_SERVICE_ID);
  return typeof service?.commitId === "string" ? service.commitId : null;
}

function deployManifestRegistration(): DeployManifestRegistration {
  const prodCommit = serviceCommitFromDeployJson("prod");
  const devCommit = serviceCommitFromDeployJson("dev");
  return {
    ok: prodCommit !== null && devCommit !== null,
    source: "deploy.json",
    serviceId: AUTH_BROKER_SERVICE_ID,
    prod: { present: prodCommit !== null, commitId: prodCommit },
    dev: { present: devCommit !== null, commitId: devCommit },
    dryRunOnly: true,
  };
}

function serviceRegistrationContract(): Record<string, unknown> {
  return {
    config: readConfigRegistration(),
    compose: composeProfileRegistration(),
    deploy: deployManifestRegistration(),
    runtimeCredentialRef: runtimeCredentialRefPresence(),
    defaultRuntimeMutation: {
      ok: true,
      mutatesCurrentProd: false,
      composeProfileRequired: AUTH_BROKER_SERVICE_ID,
      publicPortPublished: false,
      liveDeployPerformed: false,
    },
  };
}

function parseOptions(args: string[]): BrokerAdapterOptions {
  const rawCommand = args[0] ?? "contract";
  if (!["contract", "credential-request", "pr-preflight", "health"].includes(rawCommand)) {
    throw new Error(`unknown auth-broker command: ${rawCommand}`);
  }
  const command = rawCommand as BrokerCommand;
  const endpoint = stringOption(args, "--endpoint");
  return {
    command,
    repo: stringOption(args, "--repo") ?? DEFAULT_REPO,
    operation: stringOption(args, "--operation") ?? (command === "pr-preflight" ? "github.pr.preflight.dry-run" : "github.auth.status"),
    dryRun: args.includes("--dry-run") || command === "contract" || command === "credential-request" || command === "pr-preflight" || command === "health",
    endpoint: endpoint === undefined ? firstConfiguredBrokerUrl() : sanitizeEndpoint(endpoint),
    base: stringOption(args, "--base") ?? DEFAULT_BASE,
    head: stringOption(args, "--head") ?? "<head-branch>",
    issueNumber: numberOption(args, "--issue"),
  };
}

function runnerEnvTokenCoverage(): Record<string, unknown> {
  const present = SECRET_ENV_KEYS.filter((key) => hasEnvKey(key));
  return {
    ok: present.length > 0,
    source: present.length > 0 ? "runner-env" : null,
    checkedKeys: SECRET_ENV_KEYS,
    presentKeys: present,
    missingKeys: SECRET_ENV_KEYS.filter((key) => !present.includes(key)),
    presenceOnly: true,
    valuesRead: false,
    valuesPrinted: false,
  };
}

function brokerCoverage(endpoint: string | null): Record<string, unknown> {
  const credential = runtimeCredentialRefPresence();
  return {
    ok: endpoint !== null,
    source: endpoint === null ? null : "auth-broker",
    endpoint: endpoint ?? null,
    credentialRef: endpoint === null ? null : credential.credentialRef.valuePreview ?? "github:<ref>",
    credentialRefPresent: credential.credentialRef.present,
    credentialRefSourceKey: credential.credentialRef.key,
    scope: endpoint === null ? null : "broker-held-github-credential",
    runnerEnvTokenRequired: false,
    valuesRead: false,
    valuesPrinted: false,
  };
}

function brokerNeededResult(options: BrokerAdapterOptions): Record<string, unknown> {
  return {
    ok: false,
    failureKind: "auth-missing",
    degradedReason: "broker-needed",
    runnerDisposition: "infra-blocked" as RunnerDisposition,
    retryable: false,
    brokerNeeded: true,
    message: "No auth broker endpoint is configured for this dry-run contract; runner env token coverage is reported only for migration diagnostics.",
    tokenCoverage: runnerEnvTokenCoverage(),
    brokerCoverage: brokerCoverage(options.endpoint),
    serviceRegistration: serviceRegistrationContract(),
    authBroker: {
      ok: false,
      source: "broker/auth-broker-needed",
      capability: "missing-token",
      nextAction: "configure-auth-broker",
      runnerEnvTokenRequired: false,
      brokerIssuedTokenAvailable: false,
      valuesRead: false,
      valuesPrinted: false,
      realPrCreateRequiresCommanderAuthorization: true,
    },
    next: [
      "configure UNIDESK_AUTH_BROKER_URL or AUTH_BROKER_URL for broker-backed runner auth",
      "keep GH_TOKEN/GITHUB_TOKEN out of ordinary runner env once broker mode is enabled",
    ],
    redaction: {
      valuesRead: false,
      valuesPrinted: false,
      forbiddenOutputKeys: ["token", "secret", "authorization", "cookie"],
    },
  };
}

function auditEventShape(options: BrokerAdapterOptions): Record<string, unknown> {
  return {
    requestId: "authbroker-contract-request",
    observedAt: "ISO-8601 timestamp",
    caller: { plane: "code-queue", taskId: null, queueId: null },
    operation: options.operation,
    repo: options.repo,
    resource: options.command === "pr-preflight"
      ? { base: options.base, head: options.head, issueNumber: options.issueNumber }
      : null,
    dryRun: true,
    credentialRef: "github:<ref>",
    credentialKind: "github-rest-token-ref",
    credentialValuePrinted: false,
    upstream: { method: "planned", path: "planned GitHub REST path without query secrets" },
    status: "HTTP status",
    ok: "boolean",
    failureKind: null,
    degradedReason: null,
    runnerDisposition: "ready|infra-blocked|business-failed",
    retryable: "boolean",
    durationMs: "integer",
    redaction: { valuesPrinted: false },
  };
}

function plannedCredentialRequest(options: BrokerAdapterOptions): Record<string, unknown> {
  return {
    requestId: "authbroker-cli-dry-run",
    caller: { plane: "manual-cli" },
    repo: options.repo,
    operation: options.operation,
    dryRun: true,
    params: options.command === "pr-preflight"
      ? { base: options.base, head: options.head, issueNumber: options.issueNumber }
      : {},
  };
}

function readyContract(options: BrokerAdapterOptions): Record<string, unknown> {
  return {
    ok: true,
    runnerDisposition: "ready" as RunnerDisposition,
    failureKind: null,
    degradedReason: null,
    brokerNeeded: false,
    dryRun: true,
    mutation: false,
    capabilities: [...DEFAULT_CAPABILITIES],
    authBroker: {
      ok: true,
      source: "auth-broker",
      capability: "broker-issued-token",
      nextAction: "use-auth-broker",
      runnerEnvTokenRequired: false,
      brokerIssuedTokenAvailable: true,
      valuesRead: false,
      valuesPrinted: false,
      realPrCreateRequiresCommanderAuthorization: true,
    },
    tokenCoverage: {
      ok: true,
      source: "auth-broker",
      scope: "broker-held-github-credential",
      runnerEnvTokenRequired: false,
      valuesRead: false,
      valuesPrinted: false,
    },
    brokerCoverage: brokerCoverage(options.endpoint),
    credentialRequest: plannedCredentialRequest(options),
    auditEventShape: auditEventShape(options),
    serviceRegistration: serviceRegistrationContract(),
    prCapabilityContract: {
      targetBranch: options.base,
      headBranch: options.head,
      authSource: "broker-issued-token",
      systemGhBinaryRequiredForWrites: false,
      preflightCreatesPr: false,
      preflightMergesPr: false,
      realPrCreateRequiresCommanderAuthorization: true,
      brokerProxy: {
        ok: true,
        operations: ["github.auth.status", "github.issue.read", "github.pr.read", "github.pr.create"],
        writesRemote: false,
      },
      pushDryRun: {
        runnerLocal: true,
        coveredByBroker: false,
      },
    },
    redaction: {
      valuesRead: false,
      valuesPrinted: false,
      secretKeysBlocked: ["token", "secret", "authorization", "cookie"],
    },
  };
}

function contractResult(options: BrokerAdapterOptions): Record<string, unknown> {
  return {
    ok: true,
    service: "auth-broker",
    phase: "p0",
    commands: [
      "bun scripts/cli.ts auth-broker contract",
      "bun scripts/cli.ts auth-broker health --dry-run",
      "bun scripts/cli.ts auth-broker credential-request --operation github.pr.create --repo pikasTech/unidesk --dry-run",
      "bun scripts/cli.ts auth-broker pr-preflight --repo pikasTech/unidesk --base master --head <head-branch> --issue 59 --dry-run",
    ],
    capabilities: [...DEFAULT_CAPABILITIES],
    permissionBoundary: {
      allowedRepos: [DEFAULT_REPO],
      liveGithubWrites: false,
      arbitraryGhApi: false,
      registryCredentials: false,
      deployPermissions: false,
    },
    serviceRegistration: serviceRegistrationContract(),
    runnerNoTokenResult: brokerNeededResult({ ...options, endpoint: null }),
    readyShape: readyContract({ ...options, endpoint: "<UNIDESK_AUTH_BROKER_URL>" }),
  };
}

export function authBrokerHelp(): unknown {
  return {
    command: "auth-broker",
    output: "json",
    usage: [
      "bun scripts/cli.ts auth-broker contract",
      "bun scripts/cli.ts auth-broker health --dry-run [--endpoint URL]",
      "bun scripts/cli.ts auth-broker credential-request --operation github.pr.create --repo pikasTech/unidesk --dry-run [--endpoint URL]",
      "bun scripts/cli.ts auth-broker pr-preflight --repo pikasTech/unidesk --base master --head <head-branch> [--issue N] --dry-run [--endpoint URL]",
    ],
    boundary: [
      "P0 adapter is contract/dry-run only and never starts a service.",
      "GH_TOKEN and GITHUB_TOKEN values are not read or printed; only key presence is reported.",
      "No dry-run command writes GitHub, registry, deploy, filesystem credential, or service state.",
    ],
    reference: "docs/reference/auth-broker.md",
  };
}

export function runAuthBrokerCommand(args: string[]): Record<string, unknown> {
  if (args.some((arg) => arg === "help" || arg === "--help" || arg === "-h")) return authBrokerHelp() as Record<string, unknown>;
  const options = parseOptions(args);
  if (options.command === "contract") return contractResult(options);

  const envCoverage = runnerEnvTokenCoverage();
  const broker = brokerCoverage(options.endpoint);
  if (broker.ok !== true) return brokerNeededResult(options);

  if (options.command === "health") {
    return {
      ok: true,
      dryRun: true,
      mutation: false,
      service: "auth-broker",
      phase: "p0",
      brokerCoverage: broker,
      tokenCoverage: envCoverage,
      serviceRegistration: serviceRegistrationContract(),
      healthRequest: {
        method: "GET",
        path: "/health",
        wouldCallBroker: false,
      },
      capabilities: [...DEFAULT_CAPABILITIES],
      redaction: { valuesRead: false, valuesPrinted: false },
    };
  }

  return readyContract(options);
}
