import {
  detectUni060Violations,
  detectUni061Violations,
  detectUni062Violations,
  type ProtocolViolation,
} from "../../../../universal/src/primitives/protocol/types.js";
import type {
  ExtendedProtocolScenario,
  ProtocolUniRule,
  ProtocolViolationsByRule,
  TransferValidationScenario,
} from "./types.js";

function isTransferScenario(
  scenario: ExtendedProtocolScenario,
): scenario is TransferValidationScenario {
  return scenario.kind === "transfer-validation";
}

export function detectUni063Violations(scenario: TransferValidationScenario): ProtocolViolation[] {
  const violations: ProtocolViolation[] = [];
  const { spec, observation } = scenario;

  if (
    observation.observedMediaType !== undefined &&
    observation.observedMediaType !== spec.declaredMediaType
  ) {
    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "media-type-mismatch",
      fact: "observed media type disagrees with declared media type",
      details: {
        declaredMediaType: spec.declaredMediaType,
        observedMediaType: observation.observedMediaType,
      },
    });
  }

  if (observation.truncated === true) {
    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "truncated-transfer",
      fact: "transfer bytes were truncated relative to declared length",
      details: {
        declaredLength: spec.declaredLength,
        observedLength: observation.observedLength,
      },
    });
  }

  if (spec.checksum !== undefined && observation.checksumMatches === false) {
    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "checksum-mismatch",
      fact: "checksum validation failed for transferred bytes",
      details: {
        expectedChecksum: spec.checksum,
      },
    });
  }

  if (
    spec.declaredLength !== undefined &&
    observation.observedLength !== undefined &&
    observation.observedLength !== spec.declaredLength
  ) {
    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "length-mismatch",
      fact: "observed byte length disagrees with declared content length",
      details: {
        declaredLength: spec.declaredLength,
        observedLength: observation.observedLength,
      },
    });
  }

  if (observation.bytes.trim().length === 0) {
    violations.push({
      scenarioId: scenario.scenarioId,
      kind: "empty-transfer",
      fact: "transfer payload bytes are empty",
    });
  }

  return violations;
}

function ruleForScenario(scenario: ExtendedProtocolScenario): ProtocolUniRule | undefined {
  if (isTransferScenario(scenario)) {
    return "UNI-063";
  }
  switch (scenario.kind) {
    case "stream-decode":
      return "UNI-060";
    case "stdout-purity":
      return "UNI-061";
    case "serialization-round-trip":
      return "UNI-062";
    default:
      return undefined;
  }
}

function detectScenarioViolations(scenario: ExtendedProtocolScenario): ProtocolViolation[] {
  if (isTransferScenario(scenario)) {
    return detectUni063Violations(scenario);
  }
  if (scenario.kind === "stream-decode") {
    return detectUni060Violations(scenario);
  }
  if (scenario.kind === "stdout-purity") {
    return detectUni061Violations(scenario);
  }
  return detectUni062Violations(scenario);
}

export function evaluateProtocolScenarios(
  scenarios: ExtendedProtocolScenario[],
): ProtocolViolationsByRule {
  const violationsByRule: ProtocolViolationsByRule = {};

  for (const scenario of scenarios) {
    const rule = ruleForScenario(scenario);
    if (rule === undefined) {
      continue;
    }
    const violations = detectScenarioViolations(scenario);
    if (violations.length === 0) {
      continue;
    }
    const existing = violationsByRule[rule] ?? [];
    violationsByRule[rule] = [...existing, ...violations];
  }

  return violationsByRule;
}

export function flattenProtocolViolations(
  violationsByRule: ProtocolViolationsByRule,
): ProtocolViolation[] {
  return PROTOCOL_RULE_ORDER.flatMap((rule) => violationsByRule[rule] ?? []);
}

const PROTOCOL_RULE_ORDER: ProtocolUniRule[] = ["UNI-060", "UNI-061", "UNI-062", "UNI-063"];
