import type { GiteaConfig, GiteaMirrorRepository } from "./platform-infra-gitea-config";
import type { RenderedCliResult } from "./output";

export interface PacBootstrapRepositoryIdentity {
  readonly id: string;
  readonly owner: string;
  readonly repo: string;
  readonly cloneUrl: string;
  readonly sourceBranch: string;
}

export function resolvePacBootstrapMirrorRepository(
  gitea: GiteaConfig,
  targetId: string,
  repository: PacBootstrapRepositoryIdentity,
): GiteaMirrorRepository {
  const candidates = gitea.sourceAuthority.repositories.filter((item) => (
    item.targetId.toLowerCase() === targetId.toLowerCase()
    && item.gitea.owner === repository.owner
    && item.gitea.name === repository.repo
    && repositoryUrlIdentity(item.gitea.readUrl) === repositoryUrlIdentity(repository.cloneUrl)
    && item.upstream.branch === repository.sourceBranch
  ));
  if (candidates.length !== 1) {
    const disposition = candidates.length === 0 ? "no exact match" : `${candidates.length} exact matches`;
    throw new Error(
      `cannot resolve the YAML-owned Gitea bootstrap repository for PaC repository ${repository.id} on ${targetId}: ${disposition}; `
      + "match targetId, gitea owner/name, read URL, and source branch in config/platform-infra/gitea.yaml",
    );
  }
  return candidates[0];
}

export function pacBootstrapYamlMatchFailure(
  targetId: string,
  consumerId: string,
  error: unknown,
): Record<string, unknown> {
  const reason = error instanceof Error ? error.message : String(error);
  const matchCount = reason.includes("no exact match") ? 0 : Number.parseInt(reason.match(/(\d+) exact matches/u)?.[1] ?? "-1", 10);
  return {
    ok: false,
    action: "platform-infra-pipelines-as-code-bootstrap",
    mutation: false,
    mode: "precondition-failed",
    target: { id: targetId },
    consumer: { id: consumerId },
    preconditions: [
      { id: "yaml-exact-match", ok: false, code: matchCount === 0 ? "yaml-repository-no-match" : "yaml-repository-multiple-matches", matchCount, detail: reason },
    ],
    stages: [],
    blockers: [{ code: matchCount === 0 ? "yaml-repository-no-match" : "yaml-repository-multiple-matches", stage: "yaml-exact-match", reason }],
    next: {
      kind: "fix-yaml",
      command: "检查 config/platform-infra/gitea.yaml 与 config/platform-infra/pipelines-as-code.yaml 的 target、owner/name 和 read URL 精确匹配",
    },
    valuesPrinted: false,
  };
}

export function projectPacBootstrapResult(result: Record<string, unknown>): Record<string, unknown> {
  if (Array.isArray(result.preconditions) && Array.isArray(result.stages) && Array.isArray(result.blockers)) return result;
  const target = record(result.target);
  const consumer = record(result.consumer);
  const repository = record(result.repository);
  const mirror = record(result.mirrorRepository);
  const githubPreflight = record(result.githubPreflight);
  const githubRepository = records(githubPreflight.repositories)[0] ?? {};
  const gitea = record(result.giteaBootstrap);
  const apply = record(result.pipelinesAsCodeApply);
  const remote = record(apply.remote);
  const admission = record(remote.admission);
  const admissionProvenance = record(remote.admissionProvenance);
  const blockers = bootstrapBlockers(githubPreflight, gitea, apply, remote);
  const dryRun = result.mode === "dry-run";
  const repositoryMissing = remote.error === "gitea-repository-missing";
  const githubReady = githubPreflight.ok === true;
  const giteaReady = gitea.ok === true;
  const giteaSkipped = gitea.mode === "skipped";
  const applySkipped = apply.mode === "skipped";
  const applyReady = apply.ok === true || (dryRun && repositoryMissing);
  const warnings = [
    ...records(result.warnings),
    ...(applySkipped || dryRun || repositoryMissing || admission.ready === true ? [] : [{
      object: { kind: "ValidatingAdmissionPolicy", id: stringValue(admissionProvenance.policyName, "PaC admission") },
      blocking: false,
      code: "pac-admission-provenance-not-ready",
      configPath: "config/platform-infra/pipelines-as-code.yaml#deliveryProvenance.admission",
      reasons: Array.isArray(admissionProvenance.reasons) ? admissionProvenance.reasons : [],
    }]),
  ];
  const next = bootstrapNext(result, blockers);
  return {
    ok: blockers.length === 0 && giteaReady && applyReady,
    action: result.action,
    mutation: result.mutation === true,
    mode: result.mode,
    target: { id: target.id },
    consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane },
    sourceAuthority: {
      upstreamRepository: mirror.upstreamRepository,
      upstreamBranch: mirror.upstreamBranch,
      github: { ok: githubReady, code: stringValue(githubRepository.code, githubReady ? "github-upstream-available" : "github-upstream-preflight-failed") },
      giteaRepository: `${stringValue(mirror.owner)}/${stringValue(mirror.repo)}`,
      giteaKey: mirror.key,
      valuesPrinted: false,
    },
    preconditions: [
      { id: "yaml-exact-match", ok: true, code: "yaml-repository-exact-match", matchCount: 1 },
      { id: "github-upstream", ok: githubReady, code: stringValue(githubRepository.code, githubReady ? "github-upstream-available" : "github-upstream-preflight-failed") },
      { id: "gitea-repository", ok: giteaSkipped ? null : giteaReady, code: giteaSkipped ? "gitea-bootstrap-skipped" : giteaReady ? (dryRun ? "gitea-bootstrap-planned" : "gitea-bootstrap-ready") : "gitea-bootstrap-failed" },
    ],
    stages: [
      { id: "gitea-init", ok: giteaSkipped ? null : giteaReady, state: giteaSkipped ? "skipped" : giteaReady ? (dryRun ? "planned" : "ready") : "failed", mutation: gitea.mutation === true },
      { id: "pac-controller", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
      { id: "pac-admission", ok: applySkipped ? null : true, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : dryRun ? "planned" : admission.ready === true ? "ready" : "warning", blocking: false, mutation: admission.applied === true },
      { id: "tekton-consumer", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
      { id: "gitops-argo", ok: applySkipped ? null : applyReady, state: applySkipped ? "skipped" : repositoryMissing ? "waiting-for-gitea-confirm" : applyReady ? (dryRun ? "planned" : "ready") : "failed", mutation: apply.mutation === true },
    ],
    repository: { id: repository.id, namespace: repository.namespace },
    warnings,
    blockers,
    next,
    valuesPrinted: false,
  };
}

export function renderPacBootstrap(result: Record<string, unknown>): RenderedCliResult {
  const projection = projectPacBootstrapResult(result);
  const target = record(projection.target);
  const consumer = record(projection.consumer);
  const source = record(projection.sourceAuthority);
  const preconditions = records(projection.preconditions);
  const stages = records(projection.stages);
  const blockers = records(projection.blockers);
  const warnings = records(projection.warnings);
  const next = record(projection.next);
  const lines = [
    "PLATFORM-INFRA PAC / TEKTON / GITOPS BOOTSTRAP",
    ...table(["TARGET", "CONSUMER", "MODE", "MUTATION", "OK"], [[stringValue(target.id), stringValue(consumer.id), stringValue(projection.mode), boolText(projection.mutation), boolText(projection.ok)]]),
    "",
    "SOURCE AUTHORITY",
    ...table(["UPSTREAM", "BRANCH", "GITEA_KEY", "GITEA_REPOSITORY"], [[stringValue(source.upstreamRepository), stringValue(source.upstreamBranch), stringValue(source.giteaKey), stringValue(source.giteaRepository)]]),
    "",
    "PRECONDITIONS",
    ...table(["PRECONDITION", "OK", "CODE"], preconditions.map((item) => [stringValue(item.id), boolText(item.ok), stringValue(item.code)])),
    "",
    "STAGES",
    ...table(["STAGE", "OK", "STATE", "MUTATION"], stages.map((item) => [stringValue(item.id), boolText(item.ok), stringValue(item.state), boolText(item.mutation)])),
    ...(blockers.length === 0 ? [] : ["", "BLOCKERS", ...blockers.map((item) => `  ${stringValue(item.code)}: ${compactLine(stringValue(item.reason))}`)]),
    ...(warnings.length === 0 ? [] : [
      "",
      "NON-BLOCKING WARNINGS",
      ...table(["OBJECT", "BLOCKING", "CODE", "CONFIG"], warnings.map((item) => [`${stringValue(record(item.object).kind)}:${stringValue(record(item.object).id)}`, boolText(item.blocking), stringValue(item.code), stringValue(item.configPath)])),
    ]),
    "",
    "NEXT",
    `  ${stringValue(next.kind)}: ${stringValue(next.command)}`,
  ];
  return { ok: projection.ok === true, command: "platform-infra pipelines-as-code bootstrap", renderedText: lines.join("\n"), contentType: "text/plain", projection };
}

function bootstrapBlockers(githubPreflight: Record<string, unknown>, gitea: Record<string, unknown>, apply: Record<string, unknown>, remote: Record<string, unknown>): Record<string, unknown>[] {
  const blockers: Record<string, unknown>[] = [];
  const githubRepository = records(githubPreflight.repositories)[0] ?? {};
  if (githubPreflight.ok === false) {
    blockers.push({ code: stringValue(githubRepository.code, stringValue(githubPreflight.code, "github-upstream-preflight-failed")), stage: "github-upstream", reason: stringValue(githubRepository.errorTail, errorText(githubPreflight)) });
    return blockers;
  }
  for (const code of Array.isArray(gitea.blockers) ? gitea.blockers.map(String) : []) {
    blockers.push({ code: code.includes("credential") ? "source-credential-unavailable" : "github-upstream-unavailable", stage: "github-upstream", reason: code });
  }
  if (gitea.ok !== true && blockers.length === 0) blockers.push({ code: "gitea-bootstrap-failed", stage: "gitea-init", reason: errorText(gitea) });
  if (apply.ok !== true && remote.error !== "gitea-repository-missing") {
    blockers.push({ code: remote.error === "gitea-credential-unavailable" ? "gitea-credential-unavailable" : "pac-apply-failed", stage: "pac-controller", reason: errorText(remote, apply) });
  }
  return blockers;
}

function bootstrapNext(result: Record<string, unknown>, blockers: Record<string, unknown>[]): Record<string, unknown> {
  const target = record(result.target);
  const consumer = record(result.consumer);
  const source = record(result.mirrorRepository);
  if (blockers.length > 0) {
    const yamlFailure = blockers.some((item) => String(item.code).startsWith("yaml-"));
    return yamlFailure
      ? { kind: "fix-yaml", command: "检查 config/platform-infra/gitea.yaml 与 config/platform-infra/pipelines-as-code.yaml 的 target、owner/name 和 read URL 精确匹配" }
      : { kind: "read-only-status", command: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${target.id} --consumer ${consumer.id}` };
  }
  if (result.mode === "dry-run") return { kind: "confirm", command: `bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target ${target.id} --consumer ${consumer.id} --confirm` };
  return { kind: "source-pr-merge-then-plan", command: `合并 ${source.upstreamRepository}@${source.upstreamBranch} 的 GitHub PR；取得完整 source commit 后执行 pipelines-as-code release plan，范围准确后再手动 trigger` };
}

function errorText(...values: Record<string, unknown>[]): string {
  for (const value of values) {
    for (const candidate of [value.error, value.stderrTail, record(value.remote).error, record(value.remote).stderrTail]) {
      if (typeof candidate === "string" && candidate.length > 0) return candidate;
    }
  }
  return "unknown-bootstrap-failure";
}

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

function records(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.map(record) : [];
}

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

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

function compactLine(value: string): string {
  return value.replace(/\s+/gu, " ").trim().slice(0, 240) || "-";
}

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("  ").trimEnd();
  return [format(headers), format(widths.map((width) => "-".repeat(width))), ...rows.map(format)];
}

function repositoryUrlIdentity(value: string): string {
  const parsed = new URL(value);
  const path = parsed.pathname.replace(/\/+$/u, "");
  return `${parsed.protocol}//${parsed.host}${path}`;
}
