import type { RenderedCliResult } from "./output";

export function renderMirrorPlan(result: Record<string, unknown>): RenderedCliResult {
  const target = record(result.target);
  const source = record(result.sourceAuthority);
  const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.upstreamRepository), stringValue(repo.upstreamBranch), stringValue(repo.readUrl), stringValue(repo.legacyDisposition)]);
  const responsibilities = arrayRecords(result.responsibilities).map((item) => [stringValue(item.name), stringValue(item.current), stringValue(item.target), stringValue(item.disposition)]);
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror plan", [
    "PLATFORM-INFRA GITEA MIRROR PLAN",
    ...table(["TARGET", "STAGE", "AUTHORITY", "REPOS"], [[stringValue(target.id), stringValue(source.stage), stringValue(source.statusAuthority), stringValue(source.repositoryCount)]]),
    "",
    "REPOSITORIES",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "UPSTREAM", "BRANCH", "GITEA_READ_URL", "LEGACY"], repos)),
    "",
    "RESPONSIBILITIES",
    ...(responsibilities.length === 0 ? ["-"] : table(["NAME", "CURRENT", "TARGET", "DISPOSITION"], responsibilities)),
    "",
    "NEXT",
    `  status: ${stringValue(next.status)}`,
    `  full: ${stringValue(next.statusFull)}`,
    `  webhook-status: ${stringValue(next.webhookStatus)}`,
    `  fix-manual-release: ${stringValue(next.fixManualRelease)}`,
    "",
    "Disclosure: credentials are summarized by presence/fingerprint only.",
  ]);
}

export function renderMirrorBootstrap(result: Record<string, unknown>): RenderedCliResult {
  const repos = arrayRecords(result.repositories).map((repo) => [
    stringValue(repo.key),
    stringValue(repo.owner, stringValue(repo.giteaOwner)),
    stringValue(repo.name, stringValue(repo.giteaRepo)),
    result.mode === "dry-run" ? "planned" : boolText(record(repo.create).ok),
  ]);
  const blockers = Array.isArray(result.blockers) ? result.blockers.map(String) : [];
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror bootstrap", [
    "PLATFORM-INFRA GITEA MIRROR BOOTSTRAP",
    ...table(["OK", "MODE", "MUTATION", "TARGET"], [[boolText(result.ok), stringValue(result.mode), boolText(result.mutation), stringValue(record(result.target).id)]]),
    "",
    "REPOSITORIES",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "OWNER", "REPO", "STATE"], repos)),
    ...(blockers.length === 0 ? [] : ["", `BLOCKERS  ${blockers.join(",")}`]),
    ...remoteErrorLines(result),
    "",
    `NEXT  ${stringValue(result.mode === "dry-run" ? next.confirm : next.webhookStatus)}`,
  ]);
}

export function renderMirrorSync(result: Record<string, unknown>): RenderedCliResult {
  const bundles = arrayRecords(result.sourceBundles).map((bundle) => [stringValue(bundle.key), stringValue(bundle.sourceCommit).slice(0, 12), stringValue(bundle.snapshotRef), stringValue(bundle.readUrl)]);
  const repos = arrayRecords(result.repositories).map((repo) => [
    stringValue(repo.key),
    boolText(repo.syncOk),
    stringValue(repo.fetchRc),
    stringValue(repo.pushRc),
    stringValue(repo.gitopsPushRc),
    stringValue(repo.sourceCommit).slice(0, 12),
    compactTail(stringValue(repo.fetchTail) || stringValue(repo.pushTail) || stringValue(repo.revparseTail)),
  ]);
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror sync", [
    "PLATFORM-INFRA GITEA MIRROR SYNC",
    ...table(["OK", "MUTATION", "TARGET"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id)]]),
    "",
    "REPOSITORIES",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "SYNC", "FETCH", "PUSH", "GITOPS", "COMMIT", "ERROR"], repos)),
    "",
    "SOURCE BUNDLES",
    ...(bundles.length === 0 ? ["-"] : table(["KEY", "COMMIT", "SNAPSHOT_REF", "READ_URL"], bundles)),
    ...remoteErrorLines(result),
    "",
    `NEXT  ${stringValue(next.status)}`,
  ]);
}

export function renderMirrorStatus(result: Record<string, unknown>): RenderedCliResult {
  const source = record(result.sourceAuthority);
  const service = record(result.serviceHealth);
  const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.mirrorState), stringValue(repo.branchCommit).slice(0, 12), stringValue(repo.snapshotCommit).slice(0, 12), stringValue(repo.snapshotRef)]);
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror status", [
    "PLATFORM-INFRA GITEA MIRROR STATUS",
    ...table(["TARGET", "SERVICE", "MIRROR_READY", "AUTHORITY"], [[stringValue(record(result.target).id), boolText(service.ok), boolText(source.mirrorReady), stringValue(source.statusAuthority)]]),
    "",
    "REPOSITORIES",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "STATE", "BRANCH", "SNAPSHOT", "SNAPSHOT_REF"], repos)),
    ...remoteErrorLines(result),
    "",
    `NEXT  ${stringValue(next.webhookStatus)}`,
  ]);
}

export function renderMirrorWebhookApply(result: Record<string, unknown>): RenderedCliResult {
  const repos = arrayRecords(result.repositories).map((repo) => [stringValue(repo.key), stringValue(repo.repository), boolText(repo.hookReady), stringValue(repo.hookId), compactTail(stringValue(repo.error))]);
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror webhook apply", [
    "PLATFORM-INFRA GITEA MIRROR WEBHOOK APPLY",
    ...table(["OK", "MUTATION", "TARGET", "URL"], [[boolText(result.ok), boolText(result.mutation), stringValue(record(result.target).id), stringValue(record(result.webhook).publicUrl)]]),
    "",
    "GITHUB HOOKS",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "REPOSITORY", "READY", "HOOK_ID", "ERROR"], repos)),
    ...remoteErrorLines(result),
    "",
    `NEXT  ${stringValue(next.webhookStatus)}`,
  ]);
}

export function renderMirrorWebhookCredentialBlocked(result: Record<string, unknown>): RenderedCliResult {
  const target = record(result.target);
  const error = record(result.error);
  const blockerIds = Array.isArray(error.blockers) ? error.blockers.map(String) : [];
  const repositories = arrayRecords(result.repositories);
  const credentials = arrayRecords(result.credentials);
  const rows = blockerIds.map((blocker) => {
    const credential = credentials.find((item) => stringValue(item.id) === blocker) ?? {};
    const permissionResult = record(credential.permissionResult);
    const permissions = record(permissionResult.permissions);
    const repoKey = blocker.includes(":") ? blocker.slice(blocker.indexOf(":") + 1) : "";
    const repository = repositories.find((item) => stringValue(item.key) === repoKey) ?? {};
    const permissionText = [
      `status=${stringValue(permissionResult.status)}`,
      `contents=${stringValue(permissions.contents)}`,
      `metadata=${stringValue(permissions.metadata)}`,
      `webhooks=${stringValue(permissions.webhooks)}`,
    ].join(",");
    return [
      stringValue(target.id),
      stringValue(credential.repository, stringValue(repository.upstreamRepository)),
      stringValue(credential.sourceRef),
      boolText(credential.present),
      stringValue(credential.fingerprint),
      permissionText,
      blocker,
      boolText(credential.valuesPrinted),
    ];
  });
  const next = record(result.next);
  return rendered(result, "platform-infra gitea mirror webhook status", [
    "PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
    ...(rows.length === 0 ? ["-"] : table(["TARGET", "REPOSITORY", "SOURCE_REF", "PRESENT", "FINGERPRINT", "PERMISSION_RESULT", "BLOCKER", "VALUES_PRINTED"], rows)),
    "",
    `NEXT  ${stringValue(next.webhookStatusFull)}`,
  ]);
}

export function renderMirrorWebhookStatus(result: Record<string, unknown>, disclosure: Record<string, unknown>): RenderedCliResult {
  const repos = arrayRecords(result.repositories).map((repo) => {
    const delivery = record(repo.selectedPushDelivery);
    const topology = record(repo.hookTopology);
    const topologyDrift = Array.isArray(topology.driftTypes) && topology.driftTypes.length > 0
      ? topology.driftTypes.join(",")
      : topology.ready === true ? "ready" : "unknown";
    const inbox = record(repo.durableInboxRecord);
    const inboxResult = record(inbox.result);
    const inboxState = stringValue(repo.durableInboxState, "missing");
    const durableProof = repo.preDurableBootstrap === true
      ? "pre-durable-bootstrap"
      : repo.durableInboxCommitted === true
        ? `${stringValue(inboxResult.disposition)}:${stringValue(inboxResult.sourceCommit).slice(0, 12)}`
        : "-";
    return [
      stringValue(repo.key),
      boolText(repo.hookReady),
      stringValue(repo.githubHead).slice(0, 12),
      stringValue(repo.branchCommit).slice(0, 12),
      stringValue(repo.snapshotCommit).slice(0, 12),
      boolText(repo.authorityMatches),
      boolText(repo.bridgeEventStale),
      topologyDrift,
      `${stringValue(delivery.deliveryId)}:${stringValue(delivery.statusCode)}:${stringValue(delivery.status)}`,
      inboxState,
      durableProof,
      compactTail(stringValue(repo.error)),
    ];
  });
  const bridge = record(result.bridge);
  const bridgeLogs = record(result.bridgeLogs);
  const recovery = record(record(bridge.durableInbox).failedDeliveryRecovery);
  const recoveryCounts = record(recovery.counts);
  const recoveryText = `${stringValue(recovery.state)}:${stringValue(recoveryCounts.pending, "0")}/${stringValue(recoveryCounts.attempted, "0")}/${stringValue(recoveryCounts["delivery-succeeded-awaiting-source"], "0")}/${stringValue(recoveryCounts["source-committed"], "0")}/${stringValue(recoveryCounts["terminal-exhausted"], "0")}`;
  const webhook = record(result.webhook);
  const retry = record(record(webhook.bridge).retry);
  const hookReconcile = record(webhook.hookReconcile);
  const retryText = `${stringValue(retry.maxAttempts)}x/${stringValue(retry.initialDelayMs)}-${stringValue(retry.maxDelayMs)}ms`;
  const selection = record(disclosure.selection);
  const selectedRepoKey = stringValue(selection.selectedRepoKey, "");
  const delivery = arrayRecords(disclosure.deliveries)[0];
  const deliveryResult = record(delivery?.result);
  const retryDetail = record(disclosure.retry);
  const logs = record(disclosure.logs);
  const logRows = arrayRecords(logs.events).map((event) => [
    stringValue(event.syncAttempt),
    boolText(event.ok),
    boolText(event.terminal),
    stringValue(event.disposition),
    stringValue(event.elapsedMs),
    compactLine(stringValue(event.error)),
  ]);
  const firstError = record(record(disclosure.diagnostics).firstError);
  const repositoryCommands = arrayRecords(record(disclosure.next).repositories).map((item) => [
    stringValue(item.key),
    stringValue(item.deliveryId),
    stringValue(item.command),
  ]);
  const drillDown = selectedRepoKey === ""
    ? [
        "",
        "DRILL-DOWN",
        ...(repositoryCommands.length === 0 ? ["-"] : table(["REPOSITORY", "DELIVERY", "COMMAND"], repositoryCommands)),
      ]
    : [
        "",
        "DELIVERY",
        ...(delivery === undefined ? ["-"] : table(["ID", "REPOSITORY", "STATE", "ATTEMPTS", "NEXT", "COMMIT", "DISPOSITION"], [[
          stringValue(delivery.deliveryId),
          stringValue(delivery.repo),
          stringValue(delivery.state),
          `${stringValue(retryDetail.cycleAttempt)}/${stringValue(retryDetail.totalAttempts)}`,
          stringValue(retryDetail.nextAttemptAt),
          stringValue(deliveryResult.sourceCommit).slice(0, 12),
          stringValue(deliveryResult.disposition),
        ]])),
        "",
        "BRIDGE EVENTS",
        ...(logRows.length === 0 ? ["-"] : table(["ATTEMPT", "OK", "TERMINAL", "DISPOSITION", "ELAPSED_MS", "ERROR"], logRows)),
        ...(firstError.message === undefined ? [] : ["", `FIRST ERROR  ${stringValue(firstError.domain)}: ${stringValue(firstError.message)}`]),
      ];
  return rendered(disclosure, "platform-infra gitea mirror webhook status", [
    "PLATFORM-INFRA GITEA MIRROR WEBHOOK STATUS",
    ...table(["TARGET", "BRIDGE", "READY", "RETRY", "RECOVERY", "URL_SHA", "LOGS"], [[stringValue(record(result.target).id), stringValue(bridge.deployment), boolText(bridge.ready), retryText, recoveryText, stringValue(hookReconcile.desiredUrlSha256), `rc=${stringValue(bridgeLogs.exitCode)}`]]),
    "",
    "GITHUB HOOKS",
    ...(repos.length === 0 ? ["-"] : table(["KEY", "HOOK", "GH_HEAD", "GITEA", "SNAPSHOT", "MATCH", "STALE", "TOPOLOGY", "DELIVERY", "DURABLE", "PROOF", "ERROR"], repos)),
    ...drillDown,
    ...remoteErrorLines(result),
    "",
    `NEXT  ${stringValue(record(disclosure.next).full)}`,
  ]);
}

export function renderPlan(result: Record<string, unknown>): RenderedCliResult {
  const config = record(result.config);
  const target = record(config.target);
  const app = record(config.app);
  const migration = record(config.migration);
  const policy = arrayRecords(result.policy);
  const failed = policy.filter((item) => item.ok === false);
  const next = record(result.next);
  return rendered(result, "platform-infra gitea plan", [
    "PLATFORM-INFRA GITEA PLAN",
    ...table(["FIELD", "VALUE", "DETAIL", "VALUE"], [
      ["TARGET", stringValue(target.id), "route", stringValue(target.route)],
      ["NAMESPACE", stringValue(target.namespace), "role", stringValue(target.role)],
      ["IMAGE", stringValue(app.image), "replicas", stringValue(app.replicas)],
      ["SERVICE", `${stringValue(app.serviceName)}:${stringValue(record(app.service).httpPort)}`, "dns", serviceDnsFromObjects(app, target)],
      ["WEBUI", stringValue(record(app.publicExposure).publicBaseUrl), "internalGit", serviceDnsFromObjects(app, target)],
      ["ACTIONS", boolText(app.actionsEnabled), "registrationDisabled", boolText(app.registrationDisabled)],
      ["MIGRATION", stringValue(migration.role), "replaces", stringValue(migration.replaces)],
      ["POLICY", failed.length === 0 ? "ok" : `failed=${failed.length}`, "valuesPrinted", "false"],
    ]),
    "",
    "NEXT",
    `  status: ${stringValue(next.status)}`,
    `  validate: ${stringValue(next.validate)}`,
    `  mirror-status: ${stringValue(next.mirrorStatus)}`,
    `  webhook-status: ${stringValue(next.webhookStatus)}`,
    `  fix-manual-release: ${stringValue(next.fixManualRelease)}`,
    "",
    "Boundary: Gitea is internal ClusterIP source authority for GH-1560; CI trigger authority is Pipelines-as-Code, not Gitea Actions/act_runner.",
    "Disclosure: Secret values are not printed; this stage does not create runner credentials.",
  ]);
}

export function renderApply(result: Record<string, unknown>): RenderedCliResult {
  const target = record(result.target);
  const payload = record(result.payloadTransport);
  const remote = record(result.remote);
  const steps = record(remote.steps);
  const serverDryRunStep = record(steps.serverDryRun);
  const legacyFrpcCleanupStep = record(steps.legacyFrpcCleanup);
  const webhookSecretStep = record(steps.webhookSyncSecret);
  const applyStep = record(steps.apply);
  const rolloutStep = record(steps.rollout);
  const webhookRolloutStep = record(steps.webhookSyncRollout);
  return rendered(result, "platform-infra gitea apply", [
    "PLATFORM-INFRA GITEA APPLY",
    ...table(["TARGET", "NAMESPACE", "MODE", "OK"], [[stringValue(target.id), stringValue(target.namespace), stringValue(result.mode), boolText(result.ok)]]),
    ...table(["TRANSPORT", "BYTES", "MATERIALIZATION", "VALUES"], [[stringValue(payload.kind), stringValue(payload.totalBytes), stringValue(payload.materialization), stringValue(payload.valuesPrinted)]]),
    "",
    "STEPS",
    ...table(["STEP", "EXIT", "DETAIL"], [
      ["serverDryRun", stringValue(serverDryRunStep.exitCode), compactLine(stringValue(serverDryRunStep.stderrTail, stringValue(serverDryRunStep.stdoutTail)))],
      ["legacy-frpc-cleanup", stringValue(legacyFrpcCleanupStep.exitCode), compactLine(stringValue(legacyFrpcCleanupStep.stderrTail, stringValue(legacyFrpcCleanupStep.stdoutTail)))],
      ["webhook-secret", stringValue(webhookSecretStep.exitCode), compactLine(stringValue(webhookSecretStep.stderrTail, stringValue(webhookSecretStep.stdoutTail)))],
      ["apply", stringValue(applyStep.exitCode), compactLine(stringValue(applyStep.stderrTail, stringValue(applyStep.stdoutTail)))],
      ["rollout", stringValue(rolloutStep.exitCode), compactLine(stringValue(rolloutStep.stderrTail, stringValue(rolloutStep.stdoutTail)))],
      ["webhook-rollout", stringValue(webhookRolloutStep.exitCode), compactLine(stringValue(webhookRolloutStep.stderrTail, stringValue(webhookRolloutStep.stdoutTail)))],
    ]),
    ...remoteErrorLines(result),
    "",
    `NEXT  bun scripts/cli.ts platform-infra gitea status --target ${stringValue(target.id)}`,
  ]);
}

export function renderStatus(result: Record<string, unknown>): RenderedCliResult {
  const summary = record(result.summary);
  const statefulSet = record(summary.statefulSet);
  const service = record(summary.service);
  const networkPolicy = record(summary.networkPolicy);
  const pods = arrayRecords(summary.pods).map((pod) => [stringValue(pod.name), stringValue(pod.phase), boolText(pod.ready), stringValue(pod.restarts)]);
  const pvcs = arrayRecords(summary.pvcs).map((pvc) => [stringValue(pvc.name), stringValue(pvc.phase), stringValue(pvc.capacity)]);
  return rendered(result, "platform-infra gitea status", [
    "PLATFORM-INFRA GITEA STATUS",
    ...table(["TARGET", "ROUTE", "NAMESPACE", "READY"], [[stringValue(summary.target), stringValue(summary.route), stringValue(summary.namespace), boolText(summary.ready)]]),
    "",
    "CONTROL",
    ...table(["CHECK", "VALUE", "DETAIL"], [
      ["statefulSet", `${stringValue(statefulSet.readyReplicas)}/${stringValue(statefulSet.desired)}`, stringValue(statefulSet.name)],
      ["service", stringValue(service.type), stringValue(summary.serviceDns)],
      ["endpoints", boolText(summary.endpointsReady), stringValue(service.clusterIP)],
      ["allow-all", boolText(networkPolicy.allowAllPresent), "NetworkPolicy"],
    ]),
    "",
    "PODS",
    ...(pods.length === 0 ? ["-"] : table(["POD", "PHASE", "READY", "RESTARTS"], pods)),
    "",
    "PVCS",
    ...(pvcs.length === 0 ? ["-"] : table(["PVC", "PHASE", "CAPACITY"], pvcs)),
    ...remoteErrorLines(result),
    "",
    `NEXT  bun scripts/cli.ts platform-infra gitea validate --target ${stringValue(summary.target)}`,
  ]);
}

function remoteErrorLines(result: Record<string, unknown>): string[] {
  if (result.ok !== false) return [];
  const remote = record(result.remote);
  const exitCode = stringValue(remote.exitCode);
  const stderr = compactLine(stringValue(remote.stderrTail));
  const stdout = compactLine(stringValue(remote.stdoutTail));
  const detail = stderr !== "-" ? stderr : stdout;
  return ["", "ERROR", ...table(["EXIT", "DETAIL"], [[exitCode, detail]])];
}

function compactTail(value: string): string {
  const line = value.split(/\r?\n/u).map((item) => item.trim()).filter(Boolean).at(-1) ?? "";
  return line.length > 96 ? `${line.slice(0, 93)}...` : line;
}

function rendered(result: Record<string, unknown>, command: string, lines: string[]): RenderedCliResult {
  return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" };
}

function serviceDnsFromObjects(app: Record<string, unknown>, target: Record<string, unknown>): string {
  const service = record(app.service);
  return `${stringValue(app.serviceName)}.${stringValue(target.namespace)}.svc.cluster.local:${stringValue(service.httpPort)}`;
}

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

function arrayRecords(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value)
    ? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record<string, unknown>[]
    : [];
}

function stringValue(value: unknown, fallback = "-"): string {
  if (typeof value === "string" && value.length > 0) return value;
  if (typeof value === "number" || typeof value === "boolean") return String(value);
  return fallback;
}

function boolText(value: unknown): string {
  return value === true ? "true" : "false";
}

function compactLine(value: string): string {
  const trimmed = value.replace(/\s+/gu, " ").trim();
  return trimmed.length > 0 ? trimmed.slice(0, 220) : "-";
}

function table(headers: string[], rows: string[][]): string[] {
  const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
  const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join("  ");
  return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)];
}
