import type { Browser } from "playwright";
import {
  classify,
  type ClassifiedRun,
  type CoverageEvent,
  type DetectorOutcome,
  type KernelStores,
  type HarnessEvent,
} from "../../../core/src/classify/classifier.js";
import { cellFailureEvent } from "../cell-harness.js";
import { asReadonlyKernelStores } from "../../../core/src/sdk/detector.js";
import type { CapabilityProfile } from "../../../core/src/sdk/capability.js";
import type { MatrixCell } from "../../../schema/src/records/context.js";
import type { CaptureOptions, CellEvidence } from "../../../playwright/src/cell-runner.js";
import { cellScreenshotArtifacts } from "../../../playwright/src/cell-original.js";
import type { AuthAdapter } from "../../../playwright/src/auth.js";
import type { RedactionRule } from "../../../playwright/src/redaction.js";
import { sha256Canonical } from "../../../schema/src/canonical.js";
import type { CoverageGap } from "../../../universal/src/oracle/witnesses.js";
import type { Detector } from "../../../core/src/sdk/detector.js";
import { runSweepsCell } from "./capture.js";
import {
  buildSweepsMatrixEvidence,
  detectDimensionReachabilityGaps,
} from "./detect.js";
import {
  dimensionReachabilityDetector,
  localeSweepDetector,
  responsiveSweepDetector,
} from "./detectors.js";
import { parseEnabledDimensions } from "./probe.js";
import type { SweepsCellEvidence, SweepsMatrixEvidence, Ui001EvidenceRecord } from "./types.js";
import { pushHarnessWithPartialEvidence } from "../partial-evidence-harness.js";

export type SweepsMatrixPlan = {
  cells: MatrixCell[];
  coverageGaps?: CoverageGap[];
  enabledDimensions?: string[];
};

export type RunSweepsMatrixInput = {
  plan: SweepsMatrixPlan;
  baseUrl: string;
  detectors: Detector<SweepsCellEvidence | SweepsMatrixEvidence, Ui001EvidenceRecord>[];
  stores: KernelStores;
  runId: string;
  seed: string;
  clockStart: string;
  browser: Browser;
  capture: CaptureOptions;
  timeouts: { navigateMs: number; settleMs: number };
  capabilities: CapabilityProfile;
  adapters?: Record<string, AuthAdapter>;
  artifactRunDir?: string;
  redactionRules?: RedactionRule[];
  onCellEvidence?: (evidence: CellEvidence) => void;
};

function browserExecutionContext(cell: MatrixCell) {
  return { kind: "browser" as const, cell: { id: cell.id } };
}

function detectorScope(cell: MatrixCell, detectorId: string) {
  return {
    id: cell.id,
    detectorId,
    surfaceId: cell.id,
  };
}

function coverageGapScope(gap: CoverageGap, detectorId: string) {
  return {
    id: `${gap.reason}:${sha256Canonical({
      kind: "sweeps-coverage-gap-scope",
      detectorId,
      gap,
    })}`,
    detectorId,
  };
}

function coverageGapContext(gap: CoverageGap) {
  return {
    kind: "browser" as const,
    cell: {
      id: `${gap.reason}:${sha256Canonical({
        kind: "sweeps-coverage-gap-cell",
        gap,
      })}`,
    },
  };
}

function witnessRefForGap(gap: CoverageGap): CoverageEvent["witnessRefs"] {
  return [
    {
      id: `${gap.reason}:${sha256Canonical({
        kind: "sweeps-coverage-gap-witness",
        gap,
      })}`,
    },
  ];
}

function coverageEventsFromGaps(
  gaps: CoverageGap[],
  detectors: Detector<SweepsCellEvidence | SweepsMatrixEvidence, Ui001EvidenceRecord>[],
): CoverageEvent[] {
  const events: CoverageEvent[] = [];
  for (const gap of gaps) {
    for (const detector of detectors) {
      events.push({
        scope: coverageGapScope(gap, detector.id),
        context: coverageGapContext(gap),
        reason: gap.reason,
        witnessRefs: witnessRefForGap(gap),
      });
    }
  }
  return events;
}

function toClassifierOutcome(
  outcome: Awaited<
    ReturnType<Detector<SweepsCellEvidence, Ui001EvidenceRecord>["evaluate"]>
  >[number],
  cell: MatrixCell,
  cellArtifacts: DetectorOutcome["artifacts"],
): DetectorOutcome {
  return {
    detector: outcome.detector,
    class: outcome.class,
    severity: outcome.severity,
    target: outcome.target,
    context: browserExecutionContext(cell),
    summary: outcome.summary,
    evidence: outcome.evidence,
    artifacts: [...outcome.artifacts, ...cellArtifacts],
    laneEligibility: outcome.laneEligibility,
    scope: detectorScope(cell, outcome.detector.id),
    violation: outcome.violation,
    ...(outcome.proofConditionMet === undefined
      ? {}
      : { proofConditionMet: outcome.proofConditionMet }),
    ...(outcome.contextDimensions === undefined
      ? {}
      : { contextDimensions: outcome.contextDimensions }),
  };
}

function resolveEnabledDimensions(
  cells: SweepsCellEvidence[],
  configured?: string[],
): string[] {
  if (configured !== undefined && configured.length > 0) {
    return [...configured].sort();
  }
  const fromCell = cells
    .map((cell) => parseEnabledDimensions(cell.cell))
    .find((dimensions) => dimensions.length > 0);
  return fromCell ?? [];
}

export async function runSweepsMatrix(input: RunSweepsMatrixInput): Promise<ClassifiedRun> {
  let clockTick = 0;
  const clock = (): string => new Date(Date.parse(input.clockStart) + clockTick++).toISOString();
  const detectorContext = {
    clock,
    seed: input.seed,
    capabilities: input.capabilities,
    readStores: asReadonlyKernelStores(input.stores),
  };

  const sortedCells = [...input.plan.cells].sort((left, right) =>
    left.id.localeCompare(right.id),
  );
  const sortedDetectors = [...input.detectors].sort((left, right) =>
    left.id.localeCompare(right.id),
  );

  const capturedCells: SweepsCellEvidence[] = [];
  const harnessEvents: HarnessEvent[] = [];

  for (const cell of sortedCells) {
    const adapter = input.adapters?.[cell.role];
    let result: Awaited<ReturnType<typeof runSweepsCell>>;
    try {
      result = await runSweepsCell({
        cell,
        baseUrl: input.baseUrl,
        browser: input.browser,
        seed: input.seed,
        clockStart: input.clockStart,
        capture: input.capture,
        timeouts: input.timeouts,
        ...(adapter === undefined ? {} : { authAdapter: adapter }),
        ...(input.artifactRunDir === undefined ? {} : { artifactRunDir: input.artifactRunDir }),
        sourceRunId: input.runId,
        ...(input.redactionRules === undefined ? {} : { redactionRules: input.redactionRules }),
      });
    } catch (error) {
      harnessEvents.push(cellFailureEvent(cell, error));
      continue;
    }

    if (result.kind === "evidence") {
      input.onCellEvidence?.(result.evidence);
      capturedCells.push(result.evidence);
      continue;
    }
    await pushHarnessWithPartialEvidence(harnessEvents, {
      outcome: result.outcome,
      cell,
      partialEvidence: result.partialEvidence,
      artifactRunDir: input.artifactRunDir,
      sourceRunId: input.runId,
      redactionRules: input.redactionRules,
    });
  }

  const enabledDimensions = resolveEnabledDimensions(
    capturedCells,
    input.plan.enabledDimensions,
  );
  const matrixEvidence = buildSweepsMatrixEvidence(
    input.runId,
    capturedCells,
    enabledDimensions,
  );

  const cellDetectors = sortedDetectors.filter(
    (detector): detector is Detector<SweepsCellEvidence, Ui001EvidenceRecord> =>
      detector.id === localeSweepDetector.id || detector.id === responsiveSweepDetector.id,
  );
  const matrixDetectors = sortedDetectors.filter(
    (detector): detector is Detector<SweepsMatrixEvidence, Ui001EvidenceRecord> =>
      detector.id === dimensionReachabilityDetector.id,
  );

  const detectorOutcomes: DetectorOutcome[] = [];

  for (const cellEvidence of capturedCells) {
    for (const detector of cellDetectors) {
      const outcomes = await detector.evaluate(cellEvidence, detectorContext);
      for (const outcome of outcomes) {
        detectorOutcomes.push(toClassifierOutcome(outcome, cellEvidence.cell, cellScreenshotArtifacts(cellEvidence)));
      }
    }
  }

  for (const detector of matrixDetectors) {
    await detector.evaluate(matrixEvidence, detectorContext);
  }

  const reachabilityGaps = detectDimensionReachabilityGaps(matrixEvidence);
  const planGaps = input.plan.coverageGaps ?? [];
  const coverageEvents = coverageEventsFromGaps(
    [...planGaps, ...reachabilityGaps],
    sortedDetectors,
  );

  return classify({
    detectorOutcomes,
    harnessEvents,
    coverageEvents,
    stores: input.stores,
    runId: input.runId,
  });
}

export { runSweepsCell } from "./capture.js";
