import { createHash } from "node:crypto";

import { rootPath } from "./config";
import { readYamlRecord } from "./platform-infra-ops-library";
import { materializeYamlComposition } from "./yaml-composition";

const configPath = "config/platform-infra/pipelines-as-code.yaml";

export interface PacAdmissionConsumerContract {
  readonly id: string;
  readonly node: string;
  readonly namespace: string;
  readonly pipeline: string;
  readonly pipelineRunPrefix: string;
  readonly markerValue: string;
  readonly executionServiceAccountName: string;
  readonly gitOps: { readonly repoUrl: string; readonly targetRevision: string };
}

export interface PacAdmissionQueueTransitionContract {
  readonly fromSpecStatus: string;
  readonly toSpecStatus: string;
  readonly fromState: string;
  readonly toState: string;
}

export interface PacAdmissionContract {
  readonly version: string;
  readonly markerAnnotation: string;
  readonly controllerUsername: string;
  readonly queueControllerUsername: string;
  readonly queueStateLabel: string;
  readonly allowedQueueTransitions: readonly PacAdmissionQueueTransitionContract[];
  readonly policyName: string;
  readonly bindingName: string;
  readonly failurePolicy: "Ignore";
  readonly validationActions: readonly ["Warn", "Audit"];
  readonly child: {
    readonly enabled: false;
    readonly parentNameAnnotation: string;
    readonly parentUidAnnotation: string;
  };
  readonly consumers: readonly PacAdmissionConsumerContract[];
}

export interface PacAdmissionDesiredIdentity {
  readonly configSha256: string;
  readonly policyName: string;
  readonly bindingName: string;
  readonly version: string;
  readonly controllerIdentity: string;
}

export function readPacAdmissionContract(): PacAdmissionContract {
  const source = readYamlRecord<Record<string, unknown>>(rootPath("config", "platform-infra", "pipelines-as-code.yaml"), "platform-infra-pipelines-as-code");
  return parsePacAdmissionContract(source);
}

export function parsePacAdmissionContract(source: Record<string, unknown>): PacAdmissionContract {
  const root = record(materializeYamlComposition(source, { label: configPath }).value, configPath);
  const provenance = record(root.deliveryProvenance, `${configPath}#deliveryProvenance`);
  const child = record(provenance.child, `${configPath}#deliveryProvenance.child`);
  const controller = record(provenance.controller, `${configPath}#deliveryProvenance.controller`);
  const queueTransition = record(provenance.queueTransition, `${configPath}#deliveryProvenance.queueTransition`);
  const queueController = record(queueTransition.controller, `${configPath}#deliveryProvenance.queueTransition.controller`);
  const admission = record(provenance.admission, `${configPath}#deliveryProvenance.admission`);
  const controllerNamespace = required(controller.namespace, "deliveryProvenance.controller.namespace");
  const controllerServiceAccount = required(controller.serviceAccountName, "deliveryProvenance.controller.serviceAccountName");
  const queueControllerNamespace = required(queueController.namespace, "deliveryProvenance.queueTransition.controller.namespace");
  const queueControllerServiceAccount = required(queueController.serviceAccountName, "deliveryProvenance.queueTransition.controller.serviceAccountName");
  const queueStateLabel = required(queueTransition.stateLabel, "deliveryProvenance.queueTransition.stateLabel");
  const allowedQueueTransitions = recordArray(queueTransition.allowed, "deliveryProvenance.queueTransition.allowed").map((transition, index) => {
    const fromSpecStatus = pipelineRunSpecStatus(transition.fromSpecStatus, `deliveryProvenance.queueTransition.allowed[${index}].fromSpecStatus`, false);
    const toSpecStatus = pipelineRunSpecStatus(transition.toSpecStatus, `deliveryProvenance.queueTransition.allowed[${index}].toSpecStatus`, true);
    return {
      fromSpecStatus,
      toSpecStatus,
      fromState: required(transition.fromState, `deliveryProvenance.queueTransition.allowed[${index}].fromState`),
      toState: required(transition.toState, `deliveryProvenance.queueTransition.allowed[${index}].toState`),
    };
  });
  if (allowedQueueTransitions.length === 0) throw new Error("deliveryProvenance.queueTransition.allowed must not be empty");
  const transitionKeys = new Set(allowedQueueTransitions.map((transition) => [transition.fromSpecStatus, transition.toSpecStatus, transition.fromState, transition.toState].join("\u0000")));
  if (transitionKeys.size !== allowedQueueTransitions.length) throw new Error("deliveryProvenance.queueTransition.allowed must be unique");
  const validationActions = stringArray(admission.validationActions, "deliveryProvenance.admission.validationActions");
  if (validationActions.length !== 2 || validationActions[0] !== "Warn" || validationActions[1] !== "Audit") {
    throw new Error("deliveryProvenance.admission.validationActions must be exactly [Warn, Audit]");
  }
  const consumers = recordArray(root.consumers, "consumers").flatMap((consumer, index) => {
    if (consumer.deliveryProvenance === undefined) return [];
    const value = record(consumer.deliveryProvenance, `consumers[${index}].deliveryProvenance`);
    if (value.required === false) return [];
    if (value.required !== true) throw new Error(`consumers[${index}].deliveryProvenance.required must be boolean true or false`);
    const gitOps = record(value.gitOps, `consumers[${index}].deliveryProvenance.gitOps`);
    return [{
      id: required(consumer.id, `consumers[${index}].id`),
      node: required(consumer.node, `consumers[${index}].node`),
      namespace: required(consumer.namespace, `consumers[${index}].namespace`),
      pipeline: required(consumer.pipeline, `consumers[${index}].pipeline`),
      pipelineRunPrefix: required(consumer.pipelineRunPrefix, `consumers[${index}].pipelineRunPrefix`),
      markerValue: required(value.markerValue, `consumers[${index}].deliveryProvenance.markerValue`),
      executionServiceAccountName: required(value.executionServiceAccountName, `consumers[${index}].deliveryProvenance.executionServiceAccountName`),
      gitOps: {
        repoUrl: required(gitOps.repoUrl, `consumers[${index}].deliveryProvenance.gitOps.repoUrl`),
        targetRevision: required(gitOps.targetRevision, `consumers[${index}].deliveryProvenance.gitOps.targetRevision`),
      },
    }];
  });
  const markerValues = new Set(consumers.map((consumer) => consumer.markerValue));
  if (markerValues.size !== consumers.length) throw new Error("deliveryProvenance markerValue must be unique per consumer");
  for (const consumer of consumers) {
    if (consumer.executionServiceAccountName === "default") throw new Error(`deliveryProvenance executionServiceAccountName for ${consumer.id} must not reserve the shared default ServiceAccount`);
  }
  const serviceAccountKeys = consumers.map((consumer) => `${consumer.node.toLowerCase()}\u0000${consumer.executionServiceAccountName}`);
  if (new Set(serviceAccountKeys).size !== serviceAccountKeys.length) throw new Error("deliveryProvenance executionServiceAccountName must be unique per target");
  const version = required(provenance.version, "deliveryProvenance.version");
  const versionMatch = version.match(/-v([1-9][0-9]*)$/u);
  if (versionMatch === null) throw new Error("deliveryProvenance.version must end with a positive -vN resource epoch");
  const resourceEpoch = `-v${versionMatch[1]}`;
  const policyName = required(admission.policyName, "deliveryProvenance.admission.policyName");
  const bindingName = required(admission.bindingName, "deliveryProvenance.admission.bindingName");
  if (!policyName.endsWith(resourceEpoch) || !bindingName.endsWith(resourceEpoch)) throw new Error(`deliveryProvenance admission resource names must end with ${resourceEpoch}`);
  return {
    version,
    markerAnnotation: required(provenance.markerAnnotation, "deliveryProvenance.markerAnnotation"),
    controllerUsername: `system:serviceaccount:${controllerNamespace}:${controllerServiceAccount}`,
    queueControllerUsername: `system:serviceaccount:${queueControllerNamespace}:${queueControllerServiceAccount}`,
    queueStateLabel,
    allowedQueueTransitions,
    policyName,
    bindingName,
    failurePolicy: literal(admission.failurePolicy, "deliveryProvenance.admission.failurePolicy", "Ignore"),
    validationActions: ["Warn", "Audit"],
    child: {
      enabled: literal(child.enabled, "deliveryProvenance.child.enabled", false),
      parentNameAnnotation: required(child.parentNameAnnotation, "deliveryProvenance.child.parentNameAnnotation"),
      parentUidAnnotation: required(child.parentUidAnnotation, "deliveryProvenance.child.parentUidAnnotation"),
    },
    consumers,
  };
}

export function pacAdmissionConsumer(consumerId: string): PacAdmissionConsumerContract | null {
  return readPacAdmissionContract().consumers.find((consumer) => consumer.id === consumerId) ?? null;
}

export function renderPacAdmissionDesiredFragment(targetId: string): string {
  const contract = readPacAdmissionContract();
  const consumers = contract.consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase());
  if (consumers.length === 0) return "";
  const annotation = contract.markerAnnotation;
  const markerPresent = `has(object.metadata.annotations) && ${cel(annotation)} in object.metadata.annotations`;
  const oldMarkerPresent = `has(oldObject.metadata.annotations) && ${cel(annotation)} in oldObject.metadata.annotations`;
  const protectedUpdate = [markerPresent, oldMarkerPresent, ...consumers.flatMap((consumer) => [candidateExpression(consumer, "object"), candidateExpression(consumer, "oldObject")])].map((item) => `(${item})`).join(" || ");
  const allowedQueueStatusTransition = queueTransitionExpression(contract, consumers, annotation);
  const immutableSpecFields = ["managedBy", "params", "pipelineRef", "pipelineSpec", "taskRunSpecs", "taskRunTemplate", "timeouts", "workspaces"] as const;
  const immutableProofKeys = [
    ["labels", "app.kubernetes.io/managed-by"],
    ["labels", "pipelinesascode.tekton.dev/event-type"],
    ["labels", "pipelinesascode.tekton.dev/repository"],
    ["labels", "pipelinesascode.tekton.dev/sha"],
    ["labels", "pipelinesascode.tekton.dev/commit"],
    ["labels", "pipelinesascode.tekton.dev/branch"],
    ["labels", "pipelinesascode.tekton.dev/sender"],
    ["annotations", "pipelinesascode.tekton.dev/controller-info"],
    ["annotations", "pipelinesascode.tekton.dev/event-type"],
    ["annotations", "pipelinesascode.tekton.dev/git-provider"],
    ["annotations", "pipelinesascode.tekton.dev/repository"],
    ["annotations", "pipelinesascode.tekton.dev/sha"],
    ["annotations", "pipelinesascode.tekton.dev/commit"],
    ["annotations", "pipelinesascode.tekton.dev/branch"],
    ["annotations", "pipelinesascode.tekton.dev/source-branch"],
    ["annotations", "pipelinesascode.tekton.dev/sender"],
    ["annotations", contract.child.parentNameAnnotation],
    ["annotations", contract.child.parentUidAnnotation],
  ] as const;
  const validations: Record<string, unknown>[] = [
    {
      expression: `request.operation != 'CREATE' || !(${markerPresent}) || request.userInfo.username == ${cel(contract.controllerUsername)}`,
      message: "reserved PaC provenance marker may only be created by the authenticated PaC controller ServiceAccount",
      reason: "Forbidden",
    },
    {
      expression: `request.operation != 'UPDATE' || ((${markerPresent}) == (${oldMarkerPresent}) && (!(${markerPresent}) || object.metadata.annotations[${cel(annotation)}] == oldObject.metadata.annotations[${cel(annotation)}]))`,
      message: "reserved PaC provenance marker is immutable",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'CREATE' || ((!has(object.metadata.annotations) || !(${cel(contract.child.parentNameAnnotation)} in object.metadata.annotations)) && (!has(object.metadata.annotations) || !(${cel(contract.child.parentUidAnnotation)} in object.metadata.annotations)))`,
      message: "child PipelineRun provenance is disabled until an admission-verified parent name and UID contract is enabled",
      reason: "Forbidden",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${immutableProofKeys.map(([map, key]) => metadataEntryEqual(map, key)).join(" && ")})`,
      message: "PaC repository, branch, revision, event, controller, sender, and parent identity proof fields are immutable",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || ((!has(object.spec.params) && !has(oldObject.spec.params)) || (has(object.spec.params) && has(oldObject.spec.params) && object.spec.params == oldObject.spec.params))`,
      message: "PaC source revision parameters are immutable",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${serviceAccountEqual()})`,
      message: "PaC execution ServiceAccount is immutable",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${pipelineRefEqual()})`,
      message: "PaC referenced Pipeline identity is immutable",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${immutableSpecFields.map(specFieldEqual).join(" && ")})`,
      message: "admission-proven PaC PipelineRun spec fields are immutable, including embedded pipelineSpec, workspaces, and timeouts",
      reason: "Invalid",
    },
    {
      expression: `request.operation != 'UPDATE' || !(${protectedUpdate}) || (${specFieldEqual("status")} || (${allowedQueueStatusTransition}))`,
      message: "PaC PipelineRun spec.status may only follow an authenticated YAML-declared queue transition",
      reason: "Invalid",
    },
    ...consumers.flatMap((consumer) => [{
      expression: `request.operation != 'CREATE' || !(${candidateExpression(consumer, "object")}) || (request.userInfo.username == ${cel(contract.controllerUsername)} && (${markerPresent}) && object.metadata.annotations[${cel(annotation)}] == ${cel(consumer.markerValue)} && ${usesServiceAccount(consumer, "object")})`,
      message: `PipelineRun candidates for ${consumer.id} require its exact admission marker and execution ServiceAccount`,
      reason: "Invalid",
    }, {
      expression: `!(${usesServiceAccount(consumer, "object")}) || ((${candidateExpression(consumer, "object")}) && (${markerPresent}) && object.metadata.annotations[${cel(annotation)}] == ${cel(consumer.markerValue)})`,
      message: `execution ServiceAccount for ${consumer.id} is reserved to its admission-proven PipelineRun candidates`,
      reason: "Forbidden",
    }]),
  ];
  const policySpec = {
    failurePolicy: contract.failurePolicy,
    matchConstraints: {
      matchPolicy: "Equivalent",
      resourceRules: [{ apiGroups: ["tekton.dev"], apiVersions: ["v1"], operations: ["CREATE", "UPDATE"], resources: ["pipelineruns"] }],
    },
    validations,
  };
  const bindingSpec = { policyName: contract.policyName, validationActions: contract.validationActions };
  const policyHash = admissionSpecSha256(targetId, contract.policyName, contract.bindingName, policySpec, bindingSpec);
  const metadata = {
    labels: { "app.kubernetes.io/managed-by": "unidesk", "app.kubernetes.io/part-of": "platform-infra" },
    annotations: {
      "argocd.argoproj.io/sync-wave": "0",
      "unidesk.ai/pac-provenance-version": contract.version,
      "unidesk.ai/pac-controller-identity": contract.controllerUsername,
      "unidesk.ai/owning-config-ref": `${configPath}#deliveryProvenance`,
      "unidesk.ai/effective-config-sha256": policyHash,
    },
  };
  const policy = {
    apiVersion: "admissionregistration.k8s.io/v1",
    kind: "ValidatingAdmissionPolicy",
    metadata: { name: contract.policyName, ...metadata },
    spec: policySpec,
  };
  const binding = {
    apiVersion: "admissionregistration.k8s.io/v1",
    kind: "ValidatingAdmissionPolicyBinding",
    metadata: {
      name: contract.bindingName,
      ...metadata,
      annotations: { ...metadata.annotations, "argocd.argoproj.io/sync-wave": "1" },
    },
    spec: bindingSpec,
  };
  return [policy, binding].map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n") + "\n";
}

export function pacAdmissionDesiredIdentity(targetId: string): PacAdmissionDesiredIdentity {
  const contract = readPacAdmissionContract();
  const manifest = renderPacAdmissionDesiredFragment(targetId);
  const policy = manifest.length === 0 ? null : record(Bun.YAML.parse(manifest.split(/^---\s*$/mu)[0] ?? ""), "rendered admission policy");
  const policyMetadata = policy === null ? {} : record(policy.metadata, "rendered admission policy metadata");
  const annotations = policy === null ? {} : record(policyMetadata.annotations, "rendered admission policy annotations");
  return {
    configSha256: policy === null
      ? `sha256:${createHash("sha256").update(stableCanonicalJson({ targetId, disabled: true })).digest("hex")}`
      : required(annotations["unidesk.ai/effective-config-sha256"], "rendered admission policy effective config sha256"),
    policyName: contract.policyName,
    bindingName: contract.bindingName,
    version: contract.version,
    controllerIdentity: contract.controllerUsername,
  };
}

function admissionSpecSha256(targetId: string, policyName: string, bindingName: string, policySpec: Record<string, unknown>, bindingSpec: Record<string, unknown>): string {
  const input = { targetId, policyName, bindingName, policySpec, bindingSpec };
  return `sha256:${createHash("sha256").update(stableCanonicalJson(input)).digest("hex")}`;
}

function stableCanonicalJson(value: unknown): string {
  if (Array.isArray(value)) return `[${value.map(stableCanonicalJson).join(",")}]`;
  if (typeof value === "object" && value !== null) {
    return `{${Object.entries(value as Record<string, unknown>).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableCanonicalJson(item)}`).join(",")}}`;
  }
  return JSON.stringify(value);
}

function candidateExpression(consumer: PacAdmissionConsumerContract, root: "object" | "oldObject"): string {
  return `${root}.metadata.namespace == ${cel(consumer.namespace)} && (${root}.metadata.name.startsWith(${cel(consumer.pipelineRunPrefix)}) || (has(${root}.metadata.labels) && ((${cel("tekton.dev/pipeline")} in ${root}.metadata.labels && ${root}.metadata.labels[${cel("tekton.dev/pipeline")}] == ${cel(consumer.pipeline)}) || (${cel("tekton.dev/pipelineName")} in ${root}.metadata.labels && ${root}.metadata.labels[${cel("tekton.dev/pipelineName")}] == ${cel(consumer.pipeline)}))) || (has(${root}.spec.pipelineRef) && has(${root}.spec.pipelineRef.name) && ${root}.spec.pipelineRef.name == ${cel(consumer.pipeline)}))`;
}

function usesServiceAccount(consumer: PacAdmissionConsumerContract, root: "object" | "oldObject"): string {
  return `has(${root}.spec.taskRunTemplate) && has(${root}.spec.taskRunTemplate.serviceAccountName) && ${root}.spec.taskRunTemplate.serviceAccountName == ${cel(consumer.executionServiceAccountName)}`;
}

function metadataEntryEqual(map: "annotations" | "labels", key: string): string {
  const current = `has(object.metadata.${map}) && ${cel(key)} in object.metadata.${map}`;
  const previous = `has(oldObject.metadata.${map}) && ${cel(key)} in oldObject.metadata.${map}`;
  return `((${current}) == (${previous}) && (!(${current}) || object.metadata.${map}[${cel(key)}] == oldObject.metadata.${map}[${cel(key)}]))`;
}

function serviceAccountEqual(): string {
  const current = "has(object.spec.taskRunTemplate) && has(object.spec.taskRunTemplate.serviceAccountName)";
  const previous = "has(oldObject.spec.taskRunTemplate) && has(oldObject.spec.taskRunTemplate.serviceAccountName)";
  return `((${current}) == (${previous}) && (!(${current}) || object.spec.taskRunTemplate.serviceAccountName == oldObject.spec.taskRunTemplate.serviceAccountName))`;
}

function pipelineRefEqual(): string {
  const current = "has(object.spec.pipelineRef)";
  const previous = "has(oldObject.spec.pipelineRef)";
  return `((${current}) == (${previous}) && (!(${current}) || object.spec.pipelineRef == oldObject.spec.pipelineRef))`;
}

function specFieldEqual(field: string): string {
  if (field === "pipelineSpec") return schemalessSpecFieldEqual(field);
  const current = `has(object.spec.${field})`;
  const previous = `has(oldObject.spec.${field})`;
  return `((${current}) == (${previous}) && (!(${current}) || object.spec.${field} == oldObject.spec.${field}))`;
}

function schemalessSpecFieldEqual(field: string): string {
  // Tekton pipelineSpec 保留未知字段且没有静态类型，必须在 dyn 根上检查存在性并做完整深度相等。
  const current = `has(dyn(object.spec).${field})`;
  const previous = `has(dyn(oldObject.spec).${field})`;
  return `((${current}) == (${previous}) && (!(${current}) || dyn(object.spec).${field} == dyn(oldObject.spec).${field}))`;
}

function specStatusState(root: "object" | "oldObject", state: string): string {
  if (state === "absent") return `!has(${root}.spec.status)`;
  return `has(${root}.spec.status) && ${root}.spec.status == ${cel(state)}`;
}

function metadataEntryState(root: "object" | "oldObject", map: "annotations" | "labels", key: string, value: string): string {
  return `has(${root}.metadata.${map}) && ${cel(key)} in ${root}.metadata.${map} && ${root}.metadata.${map}[${cel(key)}] == ${cel(value)}`;
}

function exactConsumerAdmissionProof(consumer: PacAdmissionConsumerContract, annotation: string): string {
  const marker = (root: "object" | "oldObject") => metadataEntryState(root, "annotations", annotation, consumer.markerValue);
  return `(${marker("oldObject")} && ${marker("object")} && ${candidateExpression(consumer, "oldObject")} && ${candidateExpression(consumer, "object")} && ${usesServiceAccount(consumer, "oldObject")} && ${usesServiceAccount(consumer, "object")})`;
}

function queueTransitionExpression(contract: PacAdmissionContract, consumers: readonly PacAdmissionConsumerContract[], annotation: string): string {
  const exactConsumerProof = consumers.map((consumer) => exactConsumerAdmissionProof(consumer, annotation)).join(" || ");
  const transitions = contract.allowedQueueTransitions.map((transition) => [
    specStatusState("oldObject", transition.fromSpecStatus),
    specStatusState("object", transition.toSpecStatus),
    metadataEntryState("oldObject", "labels", contract.queueStateLabel, transition.fromState),
    metadataEntryState("object", "labels", contract.queueStateLabel, transition.toState),
    `(${exactConsumerProof})`,
  ].join(" && "));
  return `request.userInfo.username == ${cel(contract.queueControllerUsername)} && (${transitions.map((transition) => `(${transition})`).join(" || ")})`;
}

function cel(value: string): string {
  return JSON.stringify(value);
}

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

function recordArray(value: unknown, path: string): Record<string, unknown>[] {
  if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
  return value.map((item, index) => record(item, `${path}[${index}]`));
}

function required(value: unknown, path: string): string {
  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 pipelineRunSpecStatus(value: unknown, path: string, allowAbsent: boolean): string {
  const status = required(value, path);
  const known = ["Cancelled", "CancelledRunFinally", "StoppedRunFinally", "PipelineRunPending"];
  if ((status === "absent" && !allowAbsent) || (status !== "absent" && !known.includes(status))) throw new Error(`${path} must be a known Tekton v1 PipelineRun spec status${allowAbsent ? " or absent" : ""}`);
  return status;
}

function stringArray(value: unknown, path: string): string[] {
  if (!Array.isArray(value) || value.length === 0 || value.some((item) => typeof item !== "string" || item.length === 0)) throw new Error(`${path} must be a non-empty string array`);
  return value as string[];
}

function literal<T extends string | boolean>(value: unknown, path: string, expected: T): T {
  if (value !== expected) throw new Error(`${path} must be ${expected}`);
  return expected;
}
