import type {
  ArtifactClosureScenario,
  ArtifactRecord,
  ClosureFixture,
  ClosureScenario,
  CurrentBuildInputs,
  InventoryEntry,
  InventoryProvenanceKind,
  InventoryProvenanceScenario,
  RuntimeReference,
  RuntimeReferenceScenario,
} from "../../../fixtures/closure/types.js";

export type { ClosureFixture };

export const CLOSURE_CAPABILITY = "universal-closure";

export type ClosureViolation = {
  scenarioId: string;
  kind: string;
  fact: string;
  details?: unknown;
};

const RUNTIME_REFERENCE_KINDS = new Set([
  "route",
  "asset",
  "module-export",
  "peer-dependency",
  "binding",
  "migration",
  "queue",
  "script",
  "executable",
  "config-path",
]);

const ARTIFACT_STATUSES = new Set([
  "current",
  "stale",
  "mixed-tree",
  "duplicated",
  "missing",
  "untracked",
]);

const INVENTORY_PROVENANCE_KINDS = new Set([
  "production",
  "test",
  "fixture",
  "generated",
  "dependency",
  "source-only",
]);

const NON_PRODUCTION_PROVENANCE = new Set([
  "test",
  "fixture",
  "generated",
  "dependency",
  "source-only",
]);

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

export function sanitizeForEvidence(value: unknown): unknown {
  if (value === undefined) {
    return undefined;
  }
  if (typeof value === "number" && !Number.isFinite(value)) {
    return String(value);
  }
  if (Array.isArray(value)) {
    return value.map((entry) => sanitizeForEvidence(entry));
  }
  if (isRecord(value)) {
    return Object.fromEntries(
      Object.entries(value)
        .filter(([, entry]) => entry !== undefined)
        .map(([key, entry]) => [key, sanitizeForEvidence(entry)]),
    );
  }
  return value;
}

export function isRuleExcepted(fixture: ClosureFixture, rule: string): boolean {
  return fixture.intentionalException?.rules.includes(rule) ?? false;
}

function parseCurrentBuildInputs(value: unknown, path: string): CurrentBuildInputs {
  if (!isRecord(value)) {
    throw new Error(`${path} must be an object`);
  }
  if (typeof value.sourceRevision !== "string" || value.sourceRevision.trim().length === 0) {
    throw new Error(`${path}.sourceRevision must be a non-empty string`);
  }
  if (typeof value.configDigest !== "string" || value.configDigest.trim().length === 0) {
    throw new Error(`${path}.configDigest must be a non-empty string`);
  }
  if (typeof value.lockDigest !== "string" || value.lockDigest.trim().length === 0) {
    throw new Error(`${path}.lockDigest must be a non-empty string`);
  }
  return {
    sourceRevision: value.sourceRevision,
    configDigest: value.configDigest,
    lockDigest: value.lockDigest,
  };
}

function inputsMatch(left: CurrentBuildInputs, right: CurrentBuildInputs): boolean {
  return (
    left.sourceRevision === right.sourceRevision &&
    left.configDigest === right.configDigest &&
    left.lockDigest === right.lockDigest
  );
}

function parseRuntimeReference(value: unknown, index: number): RuntimeReference {
  if (!isRecord(value)) {
    throw new Error(`references[${String(index)}] must be an object`);
  }
  if (typeof value.referenceId !== "string" || value.referenceId.trim().length === 0) {
    throw new Error(`references[${String(index)}].referenceId must be a non-empty string`);
  }
  if (typeof value.kind !== "string" || !RUNTIME_REFERENCE_KINDS.has(value.kind)) {
    throw new Error(`references[${String(index)}].kind is invalid`);
  }
  if (typeof value.target !== "string" || value.target.trim().length === 0) {
    throw new Error(`references[${String(index)}].target must be a non-empty string`);
  }
  if (typeof value.packagedEnvironment !== "string" || value.packagedEnvironment.trim().length === 0) {
    throw new Error(`references[${String(index)}].packagedEnvironment must be a non-empty string`);
  }
  if (typeof value.resolved !== "boolean") {
    throw new Error(`references[${String(index)}].resolved must be a boolean`);
  }
  if (
    value.resolutionError !== undefined &&
    (typeof value.resolutionError !== "string" || value.resolutionError.trim().length === 0)
  ) {
    throw new Error(`references[${String(index)}].resolutionError must be a non-empty string`);
  }

  return {
    referenceId: value.referenceId,
    kind: value.kind as RuntimeReference["kind"],
    target: value.target,
    packagedEnvironment: value.packagedEnvironment,
    resolved: value.resolved,
    ...(typeof value.resolutionError === "string"
      ? { resolutionError: value.resolutionError }
      : {}),
  };
}

function parseRuntimeReferenceScenario(value: Record<string, unknown>, index: number): RuntimeReferenceScenario {
  if (!Array.isArray(value.references) || value.references.length === 0) {
    throw new Error(`scenarios[${String(index)}].references must be a non-empty array`);
  }
  return {
    scenarioId: typeof value.scenarioId === "string" ? value.scenarioId : "",
    kind: "runtime-reference",
    references: value.references.map((reference, referenceIndex) =>
      parseRuntimeReference(reference, referenceIndex),
    ),
  };
}

function parseArtifactRecord(value: unknown, index: number): ArtifactRecord {
  if (!isRecord(value)) {
    throw new Error(`artifacts[${String(index)}] must be an object`);
  }
  if (typeof value.artifactId !== "string" || value.artifactId.trim().length === 0) {
    throw new Error(`artifacts[${String(index)}].artifactId must be a non-empty string`);
  }
  if (typeof value.path !== "string" || value.path.trim().length === 0) {
    throw new Error(`artifacts[${String(index)}].path must be a non-empty string`);
  }
  if (typeof value.present !== "boolean") {
    throw new Error(`artifacts[${String(index)}].present must be a boolean`);
  }
  if (typeof value.status !== "string" || !ARTIFACT_STATUSES.has(value.status)) {
    throw new Error(`artifacts[${String(index)}].status is invalid`);
  }
  if (value.detail !== undefined && (typeof value.detail !== "string" || value.detail.trim().length === 0)) {
    throw new Error(`artifacts[${String(index)}].detail must be a non-empty string`);
  }

  const record: ArtifactRecord = {
    artifactId: value.artifactId,
    path: value.path,
    present: value.present,
    status: value.status as ArtifactRecord["status"],
    ...(typeof value.detail === "string" ? { detail: value.detail } : {}),
  };

  if (value.derivedFrom !== undefined) {
    record.derivedFrom = parseCurrentBuildInputs(
      value.derivedFrom,
      `artifacts[${String(index)}].derivedFrom`,
    );
  }

  return record;
}

function parseArtifactClosureScenario(value: Record<string, unknown>, index: number): ArtifactClosureScenario {
  if (!isRecord(value.currentInputs)) {
    throw new Error(`scenarios[${String(index)}].currentInputs must be an object`);
  }
  if (!Array.isArray(value.artifacts) || value.artifacts.length === 0) {
    throw new Error(`scenarios[${String(index)}].artifacts must be a non-empty array`);
  }

  return {
    scenarioId: typeof value.scenarioId === "string" ? value.scenarioId : "",
    kind: "artifact-closure",
    currentInputs: parseCurrentBuildInputs(
      value.currentInputs,
      `scenarios[${String(index)}].currentInputs`,
    ),
    artifacts: value.artifacts.map((artifact, artifactIndex) =>
      parseArtifactRecord(artifact, artifactIndex),
    ),
  };
}

function parseInventoryEntry(value: unknown, index: number): InventoryEntry {
  if (!isRecord(value)) {
    throw new Error(`entries[${String(index)}] must be an object`);
  }
  if (typeof value.surfaceId !== "string" || value.surfaceId.trim().length === 0) {
    throw new Error(`entries[${String(index)}].surfaceId must be a non-empty string`);
  }
  if (typeof value.path !== "string" || value.path.trim().length === 0) {
    throw new Error(`entries[${String(index)}].path must be a non-empty string`);
  }
  if (
    typeof value.actualProvenance !== "string" ||
    !INVENTORY_PROVENANCE_KINDS.has(value.actualProvenance)
  ) {
    throw new Error(`entries[${String(index)}].actualProvenance is invalid`);
  }
  if (
    value.declaredProvenance !== undefined &&
    (typeof value.declaredProvenance !== "string" ||
      !INVENTORY_PROVENANCE_KINDS.has(value.declaredProvenance))
  ) {
    throw new Error(`entries[${String(index)}].declaredProvenance is invalid`);
  }
  if (typeof value.includedInProductionInventory !== "boolean") {
    throw new Error(`entries[${String(index)}].includedInProductionInventory must be a boolean`);
  }

  const entry: InventoryEntry = {
    surfaceId: value.surfaceId,
    path: value.path,
    actualProvenance: value.actualProvenance as InventoryProvenanceKind,
    includedInProductionInventory: value.includedInProductionInventory,
  };
  if (typeof value.declaredProvenance === "string") {
    entry.declaredProvenance = value.declaredProvenance as InventoryProvenanceKind;
  }
  return entry;
}

function parseInventoryProvenanceScenario(
  value: Record<string, unknown>,
  index: number,
): InventoryProvenanceScenario {
  if (!Array.isArray(value.entries) || value.entries.length === 0) {
    throw new Error(`scenarios[${String(index)}].entries must be a non-empty array`);
  }

  return {
    scenarioId: typeof value.scenarioId === "string" ? value.scenarioId : "",
    kind: "inventory-provenance",
    entries: value.entries.map((entry, entryIndex) => parseInventoryEntry(entry, entryIndex)),
  };
}

function parseScenario(value: unknown, index: number): ClosureScenario {
  if (!isRecord(value)) {
    throw new Error(`scenarios[${String(index)}] must be an object`);
  }
  if (typeof value.scenarioId !== "string" || value.scenarioId.trim().length === 0) {
    throw new Error(`scenarios[${String(index)}].scenarioId must be a non-empty string`);
  }
  if (typeof value.kind !== "string") {
    throw new Error(`scenarios[${String(index)}].kind must be a string`);
  }

  switch (value.kind) {
    case "runtime-reference":
      return parseRuntimeReferenceScenario(value, index);
    case "artifact-closure":
      return parseArtifactClosureScenario(value, index);
    case "inventory-provenance":
      return parseInventoryProvenanceScenario(value, index);
    default:
      throw new Error(`unsupported scenario kind: ${value.kind}`);
  }
}

export function parseClosureFixture(input: unknown): ClosureFixture {
  if (!isRecord(input)) {
    throw new Error("closure fixture must be an object");
  }
  if (typeof input.fixtureId !== "string" || input.fixtureId.trim().length === 0) {
    throw new Error("fixtureId must be a non-empty string");
  }
  if (typeof input.closureVersion !== "string" || input.closureVersion.trim().length === 0) {
    throw new Error("closureVersion must be a non-empty string");
  }
  if (!Array.isArray(input.scenarios) || input.scenarios.length === 0) {
    throw new Error("scenarios must be a non-empty array");
  }

  const fixture: ClosureFixture = {
    fixtureId: input.fixtureId,
    closureVersion: input.closureVersion,
    scenarios: input.scenarios.map((scenario, index) => parseScenario(scenario, index)),
  };

  if (isRecord(input.intentionalException)) {
    const rules = input.intentionalException.rules;
    const reason = input.intentionalException.reason;
    if (!Array.isArray(rules) || rules.some((rule) => typeof rule !== "string")) {
      throw new Error("intentionalException.rules must be a string array");
    }
    const parsedRules = rules.filter((rule): rule is string => typeof rule === "string");
    if (parsedRules.length !== rules.length) {
      throw new Error("intentionalException.rules must be a string array");
    }
    if (typeof reason !== "string" || reason.trim().length === 0) {
      throw new Error("intentionalException.reason must be a non-empty string");
    }
    fixture.intentionalException = { rules: parsedRules, reason };
  }

  return fixture;
}

export function detectUni080Violations(scenario: ClosureScenario): ClosureViolation[] {
  if (scenario.kind !== "runtime-reference") {
    return [];
  }

  const violations: ClosureViolation[] = [];

  for (const reference of scenario.references) {
    if (reference.resolved) {
      continue;
    }

    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "unresolved-runtime-reference",
      fact: `${reference.kind} reference did not resolve in packaged environment`,
      details: sanitizeForEvidence({
        referenceId: reference.referenceId,
        kind: reference.kind,
        target: reference.target,
        packagedEnvironment: reference.packagedEnvironment,
        resolutionError: reference.resolutionError,
      }),
    });
  }

  return violations;
}

function artifactViolatesClosure(
  artifact: ArtifactRecord,
  currentInputs: CurrentBuildInputs,
): ClosureViolation | undefined {
  if (artifact.status !== "current") {
    return {
      scenarioId: "",
      kind: artifact.status,
      fact: `deploy artifact is ${artifact.status} relative to current source/config/lock inputs`,
      details: sanitizeForEvidence({
        artifactId: artifact.artifactId,
        path: artifact.path,
        present: artifact.present,
        status: artifact.status,
        derivedFrom: artifact.derivedFrom,
        detail: artifact.detail,
      }),
    };
  }

  if (!artifact.present) {
    return {
      scenarioId: "",
      kind: "missing",
      fact: "required deploy artifact is absent",
      details: sanitizeForEvidence({
        artifactId: artifact.artifactId,
        path: artifact.path,
        detail: artifact.detail,
      }),
    };
  }

  if (artifact.derivedFrom === undefined) {
    return {
      scenarioId: "",
      kind: "untracked",
      fact: "deploy artifact lacks derivation from current source/config/lock inputs",
      details: sanitizeForEvidence({
        artifactId: artifact.artifactId,
        path: artifact.path,
        detail: artifact.detail,
      }),
    };
  }

  if (!inputsMatch(artifact.derivedFrom, currentInputs)) {
    return {
      scenarioId: "",
      kind: "stale",
      fact: "deploy artifact derived from stale source/config/lock inputs",
      details: sanitizeForEvidence({
        artifactId: artifact.artifactId,
        path: artifact.path,
        derivedFrom: artifact.derivedFrom,
        currentInputs,
        detail: artifact.detail,
      }),
    };
  }

  return undefined;
}

export function detectUni081Violations(scenario: ClosureScenario): ClosureViolation[] {
  if (scenario.kind !== "artifact-closure") {
    return [];
  }

  const violations: ClosureViolation[] = [];
  const pathsSeen = new Map<string, ArtifactRecord[]>();

  for (const artifact of scenario.artifacts) {
    const entries = pathsSeen.get(artifact.path) ?? [];
    entries.push(artifact);
    pathsSeen.set(artifact.path, entries);

    const violation = artifactViolatesClosure(artifact, scenario.currentInputs);
    if (violation !== undefined) {
      violations.push({
        ...violation,
        scenarioId: scenario.scenarioId,
      });
    }
  }

  for (const [path, artifacts] of pathsSeen) {
    if (artifacts.length <= 1) {
      continue;
    }

    const alreadyReported = violations.some(
      (violation) =>
        violation.scenarioId === scenario.scenarioId &&
        violation.kind === "duplicated" &&
        isRecord(violation.details) &&
        violation.details.path === path,
    );
    if (alreadyReported) {
      continue;
    }

    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "duplicated",
      fact: "multiple deploy artifacts claim the same packaged path",
      details: sanitizeForEvidence({
        path,
        artifactIds: artifacts.map((artifact) => artifact.artifactId),
      }),
    });
  }

  return violations;
}

function inventoryEntryViolatesProvenance(entry: InventoryEntry): ClosureViolation | undefined {
  if (!entry.includedInProductionInventory) {
    return undefined;
  }

  if (NON_PRODUCTION_PROVENANCE.has(entry.actualProvenance)) {
    return {
      scenarioId: "",
      kind: "non-production-surface-inventory",
      fact: `${entry.actualProvenance} surface silently entered production inventory`,
      details: sanitizeForEvidence(entry),
    };
  }

  if (entry.declaredProvenance === undefined) {
    return {
      scenarioId: "",
      kind: "missing-provenance",
      fact: "production inventory entry lacks declared provenance",
      details: sanitizeForEvidence(entry),
    };
  }

  if (entry.declaredProvenance !== entry.actualProvenance) {
    return {
      scenarioId: "",
      kind: "mislabeled-provenance",
      fact: "production inventory entry mislabels actual provenance",
      details: sanitizeForEvidence(entry),
    };
  }

  return undefined;
}

export function detectUni083Violations(scenario: ClosureScenario): ClosureViolation[] {
  if (scenario.kind !== "inventory-provenance") {
    return [];
  }

  const violations: ClosureViolation[] = [];

  for (const entry of scenario.entries) {
    const violation = inventoryEntryViolatesProvenance(entry);
    if (violation !== undefined) {
      violations.push({
        ...violation,
        scenarioId: scenario.scenarioId,
      });
    }
  }

  return violations;
}
