import type {
  ArtifactClosureScenario,
  ClosureScenario,
  InventoryProvenanceScenario,
  RuntimeReferenceScenario,
} from "../../../../universal/fixtures/closure/types.js";
import {
  CLOSURE_CASE_KINDS,
  type ClosureCase,
  type ClosureCaseKind,
  type ClosureContractSpec,
  type ClosureInventory,
  type ClosureInventoryEntry,
  type ClosureInventorySpec,
  type ClosureUniRule,
  type EnvironmentBinding,
  type EnvironmentBindingScenario,
} from "./types.js";

function assertNonEmptyString(value: string, field: string): void {
  if (value.trim().length === 0) {
    throw new Error(`${field} must be a non-empty string`);
  }
}

function assertCaseKind(value: string): asserts value is ClosureCaseKind {
  if (!(CLOSURE_CASE_KINDS as readonly string[]).includes(value)) {
    throw new Error(`unknown closure case kind: ${value}`);
  }
}

function caseRule(kind: ClosureCaseKind): ClosureUniRule {
  switch (kind) {
    case "runtime-file-presence":
      return "UNI-080";
    case "stale-artifact":
      return "UNI-081";
    case "accidental-test-route":
      return "UNI-083";
    case "absent-environment-binding":
      return "UNI-082";
    case "descriptor-cleanup":
    case "lock-cleanup":
    case "process-cleanup":
      return "UNI-051";
    case "empty-selection":
      return "UNI-084";
    default: {
      const exhaustive: never = kind;
      throw new Error(`unsupported closure case kind: ${String(exhaustive)}`);
    }
  }
}

function inventoryViolations(entry: ClosureInventoryEntry): string[] {
  const violations: string[] = [];
  if (entry.sourcePresent && !entry.packagedReachable && entry.includedInProductionInventory) {
    violations.push("source presence cannot certify packaged reachability");
  }
  if (entry.actualProvenance === "source-only" && entry.packagedReachable) {
    violations.push("source-only surface cannot be packaged reachable");
  }
  return violations;
}

export function buildClosureInventory(spec: ClosureInventorySpec): ClosureInventory {
  assertNonEmptyString(spec.id, "id");
  assertNonEmptyString(spec.environment, "environment");
  if (!Array.isArray(spec.entries) || spec.entries.length === 0) {
    throw new Error("entries must be a non-empty array");
  }

  for (const entry of spec.entries) {
    assertNonEmptyString(entry.surfaceId, "surfaceId");
    assertNonEmptyString(entry.path, "path");
    const violations = inventoryViolations(entry);
    if (violations.length > 0) {
      throw new Error(`${entry.surfaceId}: ${violations.join("; ")}`);
    }
  }

  return {
    id: spec.id,
    environment: spec.environment,
    entries: spec.entries.map((entry) => ({ ...entry })),
  };
}

function runtimeReferenceScenario(
  scenarioId: string,
  referenceId: string,
  kind: RuntimeReferenceScenario["references"][number]["kind"],
  target: string,
  environment: string,
  resolved: boolean,
  resolutionError?: string,
): RuntimeReferenceScenario {
  return {
    scenarioId,
    kind: "runtime-reference",
    references: [
      {
        referenceId,
        kind,
        target,
        packagedEnvironment: environment,
        resolved,
        ...(resolutionError !== undefined ? { resolutionError } : {}),
      },
    ],
  };
}

function artifactScenario(
  scenarioId: string,
  currentInputs: ClosureContractSpec["currentInputs"],
  artifactId: string,
  path: string,
  status: ArtifactClosureScenario["artifacts"][number]["status"],
  present: boolean,
  derivedFrom?: ArtifactClosureScenario["artifacts"][number]["derivedFrom"],
  detail?: string,
): ArtifactClosureScenario {
  return {
    scenarioId,
    kind: "artifact-closure",
    currentInputs,
    artifacts: [
      {
        artifactId,
        path,
        present,
        status,
        ...(derivedFrom !== undefined ? { derivedFrom } : {}),
        ...(detail !== undefined ? { detail } : {}),
      },
    ],
  };
}

function inventoryScenario(
  scenarioId: string,
  entries: InventoryProvenanceScenario["entries"],
): InventoryProvenanceScenario {
  return {
    scenarioId,
    kind: "inventory-provenance",
    entries,
  };
}

function environmentBindingScenario(
  scenarioId: string,
  environment: string,
  bindings: EnvironmentBinding[],
): EnvironmentBindingScenario {
  return {
    scenarioId,
    kind: "environment-binding",
    environment,
    bindings,
  };
}

export function generateClosureCases(spec: ClosureContractSpec): ClosureCase[] {
  assertNonEmptyString(spec.id, "id");
  assertNonEmptyString(spec.closureVersion, "closureVersion");
  assertNonEmptyString(spec.environment, "environment");
  const inventory = buildClosureInventory(spec.inventory);

  const cases: ClosureCase[] = [
    {
      caseId: `${spec.id}:runtime-file-presence`,
      kind: "runtime-file-presence",
      rule: "UNI-080",
      description: "runtime file references must resolve in packaged environment",
      scenario: runtimeReferenceScenario(
        "runtime-file-presence",
        "runtime-file",
        "asset",
        "dist/runtime/app.wasm",
        spec.environment,
        true,
      ),
    },
    {
      caseId: `${spec.id}:stale-artifact`,
      kind: "stale-artifact",
      rule: "UNI-081",
      description: "deploy artifacts must derive from current source/config/lock inputs",
      scenario: artifactScenario(
        "stale-artifact",
        spec.currentInputs,
        "deploy-bundle",
        "dist/server/index.js",
        "current",
        true,
        spec.currentInputs,
      ),
    },
    {
      caseId: `${spec.id}:accidental-test-route`,
      kind: "accidental-test-route",
      rule: "UNI-083",
      description: "test routes must not silently enter production inventory",
      scenario: inventoryScenario(
        "accidental-test-route",
        inventory.entries.map((entry) => ({
          surfaceId: entry.surfaceId,
          path: entry.path,
          actualProvenance: entry.actualProvenance,
          ...(entry.declaredProvenance !== undefined
            ? { declaredProvenance: entry.declaredProvenance }
            : {}),
          includedInProductionInventory: entry.includedInProductionInventory,
        })),
      ),
    },
    {
      caseId: `${spec.id}:empty-selection`,
      kind: "empty-selection",
      rule: "UNI-084",
      description: "empty selection without expected-empty contract yields coverage-incomplete",
      scenario: inventoryScenario("empty-selection", []),
    },
  ];

  if (spec.bindings !== undefined && spec.bindings.length > 0) {
    cases.push({
      caseId: `${spec.id}:absent-environment-binding`,
      kind: "absent-environment-binding",
      rule: "UNI-082",
      description: "required production bindings referenced in code must be present",
      scenario: environmentBindingScenario(
        "absent-environment-binding",
        spec.environment,
        spec.bindings,
      ),
    });
  }

  for (const entry of cases) {
    assertCaseKind(entry.kind);
    if (entry.rule !== caseRule(entry.kind)) {
      throw new Error(`closure case ${entry.caseId} maps to unexpected rule ${entry.rule}`);
    }
  }

  return cases;
}

export function closureCaseKindsForRule(rule: ClosureUniRule): ClosureCaseKind[] {
  return CLOSURE_CASE_KINDS.filter((kind) => caseRule(kind) === rule);
}

export function inventoryToScenarios(inventory: ClosureInventory): ClosureScenario[] {
  return [
    inventoryScenario(
      "inventory-snapshot",
      inventory.entries.map((entry) => ({
        surfaceId: entry.surfaceId,
        path: entry.path,
        actualProvenance: entry.actualProvenance,
        ...(entry.declaredProvenance !== undefined
          ? { declaredProvenance: entry.declaredProvenance }
          : {}),
        includedInProductionInventory: entry.includedInProductionInventory,
      })),
    ),
  ];
}
