import { mkdtemp } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { ExecutionContext } from "../../../../schema/src/records/context.js";
import {
  classify,
  type DetectorOutcome,
  type KernelStores,
} from "../../../../core/src/classify/classifier.js";
import { loadContractStore } from "../../../../core/src/contracts/store.js";
import { loadDecisionStore } from "../../../../core/src/decisions/store.js";
import {
  AUTHORITY_FIELDS,
  type AuthorityField,
  type ProvenanceAuthorityAttempt,
  type ProvenanceAuthorityResult,
  type ProvenanceAuthoritySpec,
} from "./types.js";

const SECURITY_DETECTOR = { id: "security-boundary", version: "1.0.0" } as const;

const UNTRUSTED_FORWARDING_HEADERS = [
  "x-forwarded-for",
  "x-real-ip",
  "x-client-ip",
  "forwarded",
  "x-forwarded-host",
] as const;

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

function securityContext(systemId: string): ExecutionContext {
  return {
    kind: "api",
    surfaceId: systemId,
    adapterId: "security-boundary",
    environment: {},
    seed: systemId,
  };
}

async function createDefaultStores(now = "2026-07-25T12:00:00.000Z"): Promise<KernelStores> {
  const root = await mkdtemp(join(tmpdir(), "invariantum-provenance-authority-"));
  const [decisions, contracts] = await Promise.all([
    loadDecisionStore(root),
    loadContractStore(root),
  ]);
  return {
    decisions,
    contracts,
    authoritativeContracts: new Set<string>(),
    now,
  };
}

function normalizeHeaderName(name: string): string {
  return name.trim().toLowerCase();
}

function isTrustedHeader(name: string, trusted: string[] | undefined): boolean {
  const normalized = normalizeHeaderName(name);
  return (trusted ?? []).map(normalizeHeaderName).includes(normalized);
}

function isUntrustedForwardingHeader(name: string): boolean {
  return (UNTRUSTED_FORWARDING_HEADERS as readonly string[]).includes(normalizeHeaderName(name));
}

function evaluateAuthorityViolations(
  attempt: ProvenanceAuthorityAttempt,
  resolved: Partial<Record<AuthorityField, unknown>>,
): string[] {
  const violated: string[] = [];

  if (attempt.clientDuplicate !== undefined) {
    for (const field of AUTHORITY_FIELDS) {
      const declared = attempt.declaredAuthority[field];
      const duplicate = attempt.clientDuplicate[field];
      if (duplicate !== undefined && declared !== undefined && resolved[field] === duplicate) {
        violated.push("UNI-040-client-duplicate-override");
        break;
      }
    }
  }

  if (attempt.untrustedHeaders !== undefined) {
    for (const [headerName, headerValue] of Object.entries(attempt.untrustedHeaders)) {
      if (!isUntrustedForwardingHeader(headerName)) {
        continue;
      }
      if (isTrustedHeader(headerName, attempt.trustedProxyHeaders)) {
        continue;
      }
      const declaredAddress = attempt.declaredAuthority.clientAddress;
      if (
        declaredAddress !== undefined &&
        typeof headerValue === "string" &&
        resolved.clientAddress === headerValue
      ) {
        violated.push("UNI-041-untrusted-header-override");
        break;
      }
    }
  }

  return violated;
}

function buildViolationOutcome(
  specId: string,
  violation: Record<string, unknown>,
): DetectorOutcome {
  return {
    detector: SECURITY_DETECTOR,
    class: "provenance-authority-violation",
    severity: "high",
    target: { kind: "provenance-authority", canonical: specId },
    context: securityContext(specId),
    summary: `Provenance authority violation for ${specId}`,
    evidence: [{ truthSource: "universal", payload: violation }],
    artifacts: [],
    laneEligibility: "blocking-eligible",
    proofConditionMet: true,
    scope: {
      id: `provenance:${specId}`,
      detectorId: SECURITY_DETECTOR.id,
      surfaceId: specId,
    },
    violation,
    contractOrConfig: { specId },
    contextDimensions: {},
  };
}

export async function checkProvenanceAuthority(
  spec: ProvenanceAuthoritySpec,
  runId = "provenance-authority-run",
): Promise<ProvenanceAuthorityResult> {
  assertNonEmptyString(spec.id, "id");
  if (typeof spec.resolve !== "function") {
    throw new Error("resolve must be a function");
  }

  const { resolved, source } = spec.resolve(spec.attempt);
  const violated = evaluateAuthorityViolations(spec.attempt, resolved);
  const holds = violated.length === 0;
  const laneEligibility = holds ? "advisory" : "blocking-eligible";

  const detectorOutcomes: DetectorOutcome[] = [];
  if (!holds) {
    detectorOutcomes.push(
      buildViolationOutcome(spec.id, {
        specId: spec.id,
        violated,
        resolved,
        source,
        attempt: spec.attempt,
      }),
    );
  }

  const stores = await createDefaultStores();
  const classified = classify({
    detectorOutcomes,
    harnessEvents: [],
    coverageEvents: [],
    stores,
    runId,
  });

  return {
    id: spec.id,
    holds,
    resolved,
    source,
    laneEligibility,
    classified,
    ...(violated.length > 0 ? { violated } : {}),
  };
}
