export interface QualificationMetricAggregate {
  micro: {
    groundIou: number;
    naturalGroundRecall: number;
    obstacleNonGroundRecall: number;
  };
  latencyMs: {
    p50: number;
    p95: number;
    maximum: number;
  };
  assignedFraction: number;
  sourceCoverage: number;
  frameCount: number;
}

export interface QualificationCheck {
  checkId: string;
  observed: number;
  operator: ">=" | "<=";
  threshold: number;
  passed: boolean;
}

export interface QualificationWorstFrame {
  frameId: string;
  currentGroundIou: number;
  patchworkGroundIou: number;
  groundIouDelta: number;
  patchworkNaturalGroundRecall: number;
}

export interface GroundQualification {
  runId: string;
  identitySha256: string;
  sourceId: string;
  split: string;
  frameCount: number;
  current: QualificationMetricAggregate;
  patchwork: QualificationMetricAggregate;
  degradations: Record<string, QualificationMetricAggregate>;
  checks: QualificationCheck[];
  worstFrames: QualificationWorstFrame[];
  decision: {
    status: "shadow-candidate" | "qualification-rejected";
    passed: boolean;
    promotedToNavigationOrSafety: false;
    reason: string;
  };
}

export interface GroundFailurePreview {
  runId: string;
  frameId: string;
  sourcePointCount: number;
  pointCount: number;
  pointsXyzM: Array<[number, number, number]>;
  groundTruthGround: number[];
  evaluated: number[];
  currentGround: number[];
  patchworkGround: number[];
  currentDisagreement: number[];
  patchworkDisagreement: number[];
}

export interface GroundReviewFrameMetrics {
  groundIou: number;
  naturalGroundRecall: number;
  obstacleNonGroundRecall: number;
  latencyMs: number;
}

export interface GroundReviewFrameSummary {
  sequence: number;
  frameId: string;
  datasetSequenceId: string;
  datasetFrameNumber: number;
  sensorTimestampNs: string;
  sourcePointCount: number;
  pointCount: number;
  current: GroundReviewFrameMetrics;
  patchwork: GroundReviewFrameMetrics;
  groundIouDelta: number;
}

export interface GroundReview {
  runId: string;
  identitySha256: string;
  sourceId: string;
  frameCount: number;
  previewPoints: number;
  frames: GroundReviewFrameSummary[];
}

export interface GroundReviewFrame extends GroundFailurePreview {
  intensity0To255: number[];
}

export class GroundQualificationContractError extends Error {}

export class GroundQualificationApiError extends Error {
  constructor(message: string, readonly status: number | null = null) {
    super(message);
  }
}

type QualificationFetch = (
  input: RequestInfo | URL,
  init?: RequestInit,
) => Promise<Response>;

const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const SHA256 = /^[a-f0-9]{64}$/;
const GOOSE_FRAME_ID =
  /^([A-Za-z0-9][A-Za-z0-9_-]{0,95})__([0-9]{4})_([0-9]{16,20})$/;

function record(value: unknown, field: string): Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    throw new GroundQualificationContractError(`${field} должен быть объектом.`);
  }
  return value as Record<string, unknown>;
}

function array(value: unknown, field: string, maximum: number): unknown[] {
  if (!Array.isArray(value) || value.length > maximum) {
    throw new GroundQualificationContractError(`${field} должен быть ограниченным массивом.`);
  }
  return value;
}

function text(value: unknown, field: string, maximum = 256): string {
  if (typeof value !== "string" || !value.trim() || value.length > maximum) {
    throw new GroundQualificationContractError(`${field} должен быть непустой строкой.`);
  }
  return value;
}

function id(value: unknown, field: string): string {
  const result = text(value, field, 128);
  if (!SAFE_ID.test(result)) {
    throw new GroundQualificationContractError(`${field} содержит недопустимый идентификатор.`);
  }
  return result;
}

function number(value: unknown, field: string, minimum = 0): number {
  if (typeof value !== "number" || !Number.isFinite(value) || value < minimum) {
    throw new GroundQualificationContractError(`${field} должен быть конечным числом.`);
  }
  return value;
}

function fraction(value: unknown, field: string): number {
  const result = number(value, field);
  if (result > 1) {
    throw new GroundQualificationContractError(`${field} должен быть долей 0..1.`);
  }
  return result;
}

function boolean(value: unknown, field: string): boolean {
  if (typeof value !== "boolean") {
    throw new GroundQualificationContractError(`${field} должен быть boolean.`);
  }
  return value;
}

function integer(value: unknown, field: string, minimum = 0): number {
  const result = number(value, field, minimum);
  if (!Number.isInteger(result)) {
    throw new GroundQualificationContractError(`${field} должен быть целым числом.`);
  }
  return result;
}

function aggregate(value: unknown, field: string): QualificationMetricAggregate {
  const source = record(value, field);
  const micro = record(source.micro, `${field}.micro`);
  const latency = record(source.latency_ms, `${field}.latency_ms`);
  return {
    micro: {
      groundIou: fraction(micro.ground_iou, `${field}.micro.ground_iou`),
      naturalGroundRecall: fraction(
        micro.natural_ground_recall,
        `${field}.micro.natural_ground_recall`,
      ),
      obstacleNonGroundRecall: fraction(
        micro.obstacle_non_ground_recall,
        `${field}.micro.obstacle_non_ground_recall`,
      ),
    },
    latencyMs: {
      p50: number(latency.p50, `${field}.latency_ms.p50`),
      p95: number(latency.p95, `${field}.latency_ms.p95`),
      maximum: number(latency.maximum, `${field}.latency_ms.maximum`),
    },
    assignedFraction: fraction(source.assigned_fraction, `${field}.assigned_fraction`),
    sourceCoverage: fraction(source.source_coverage, `${field}.source_coverage`),
    frameCount: number(source.frame_count, `${field}.frame_count`, 1),
  };
}

export function decodeGroundQualification(payload: unknown): GroundQualification {
  const source = record(payload, "qualification");
  if (
    source.schema_version !== "missioncore.polygon-ground-qualification/v1"
    || source.access !== "read-only"
  ) {
    throw new GroundQualificationContractError("Неизвестная схема квалификации.");
  }
  const runId = id(source.run_id, "run_id");
  const identitySha256 = text(source.identity_sha256, "identity_sha256", 64);
  if (!SHA256.test(identitySha256)) {
    throw new GroundQualificationContractError("identity_sha256 должен содержать SHA-256.");
  }
  const aggregates = record(source.aggregates, "aggregates");
  const degradationSource = record(source.degradations, "degradations");
  const degradations: Record<string, QualificationMetricAggregate> = {};
  Object.entries(degradationSource).forEach(([profileId, value]) => {
    if (!SAFE_ID.test(profileId) || Object.keys(degradations).length >= 32) {
      throw new GroundQualificationContractError("Профили деградации имеют неверный контракт.");
    }
    degradations[profileId] = aggregate(value, `degradations.${profileId}`);
  });
  const checks = array(source.checks, "checks", 128).map((value, index) => {
    const check = record(value, `checks[${index}]`);
    const rawOperator = text(check.operator, `checks[${index}].operator`, 2);
    if (rawOperator !== ">=" && rawOperator !== "<=") {
      throw new GroundQualificationContractError("Проверка содержит неизвестный оператор.");
    }
    const operator: QualificationCheck["operator"] = rawOperator;
    return {
      checkId: id(check.check_id, `checks[${index}].check_id`),
      observed: number(check.observed, `checks[${index}].observed`, -Infinity),
      operator,
      threshold: number(check.threshold, `checks[${index}].threshold`, -Infinity),
      passed: boolean(check.passed, `checks[${index}].passed`),
    };
  });
  const worstFrames = array(source.worst_frames, "worst_frames", 20).map(
    (value, index) => {
      const frame = record(value, `worst_frames[${index}]`);
      return {
        frameId: id(frame.frame_id, `worst_frames[${index}].frame_id`),
        currentGroundIou: fraction(
          frame.current_ground_iou,
          `worst_frames[${index}].current_ground_iou`,
        ),
        patchworkGroundIou: fraction(
          frame.patchwork_ground_iou,
          `worst_frames[${index}].patchwork_ground_iou`,
        ),
        groundIouDelta: number(
          frame.ground_iou_delta,
          `worst_frames[${index}].ground_iou_delta`,
          -1,
        ),
        patchworkNaturalGroundRecall: fraction(
          frame.patchwork_natural_ground_recall,
          `worst_frames[${index}].patchwork_natural_ground_recall`,
        ),
      };
    },
  );
  const decision = record(source.decision, "decision");
  const status = text(decision.status, "decision.status", 64);
  if (status !== "shadow-candidate" && status !== "qualification-rejected") {
    throw new GroundQualificationContractError("Решение квалификации неизвестно.");
  }
  if (decision.promoted_to_navigation_or_safety !== false) {
    throw new GroundQualificationContractError(
      "Квалификация не может принимать навигацию или safety.",
    );
  }
  const frameCount = number(source.frame_count, "frame_count", 1);
  const current = aggregate(aggregates.current, "aggregates.current");
  const patchwork = aggregate(aggregates.patchworkpp, "aggregates.patchworkpp");
  if (current.frameCount !== frameCount || patchwork.frameCount !== frameCount) {
    throw new GroundQualificationContractError("Агрегаты не совпадают с числом кадров.");
  }
  return {
    runId,
    identitySha256,
    sourceId: text(source.source_id, "source_id"),
    split: id(source.split, "split"),
    frameCount,
    current,
    patchwork,
    degradations,
    checks,
    worstFrames,
    decision: {
      status,
      passed: boolean(decision.passed, "decision.passed"),
      promotedToNavigationOrSafety: false,
      reason: text(decision.reason, "decision.reason", 512),
    },
  };
}

function mask(value: unknown, field: string, expected: number): number[] {
  const values = array(value, field, 50_000).map((item, index) => {
    if (item !== 0 && item !== 1) {
      throw new GroundQualificationContractError(`${field}[${index}] должен быть 0 или 1.`);
    }
    return item;
  });
  if (values.length !== expected) {
    throw new GroundQualificationContractError(`${field} не совпадает с point_count.`);
  }
  return values as number[];
}

export function decodeGroundFailurePreview(payload: unknown): GroundFailurePreview {
  const source = record(payload, "failure preview");
  if (
    source.schema_version !== "missioncore.polygon-ground-failure-preview/v1"
    || source.access !== "read-only"
  ) {
    throw new GroundQualificationContractError("Неизвестная схема preview.");
  }
  const pointCount = number(source.point_count, "point_count", 1);
  const pointsXyzM = array(source.points_xyz_m, "points_xyz_m", 50_000).map(
    (value, index): [number, number, number] => {
      const tuple = array(value, `points_xyz_m[${index}]`, 3);
      if (tuple.length !== 3) {
        throw new GroundQualificationContractError("Точка должна содержать XYZ.");
      }
      return [
        number(tuple[0], `points_xyz_m[${index}].x`, -Infinity),
        number(tuple[1], `points_xyz_m[${index}].y`, -Infinity),
        number(tuple[2], `points_xyz_m[${index}].z`, -Infinity),
      ];
    },
  );
  if (pointsXyzM.length !== pointCount) {
    throw new GroundQualificationContractError("points_xyz_m не совпадает с point_count.");
  }
  return {
    runId: id(source.run_id, "run_id"),
    frameId: id(source.frame_id, "frame_id"),
    sourcePointCount: number(source.source_point_count, "source_point_count", pointCount),
    pointCount,
    pointsXyzM,
    groundTruthGround: mask(source.ground_truth_ground, "ground_truth_ground", pointCount),
    evaluated: mask(source.evaluated, "evaluated", pointCount),
    currentGround: mask(source.current_ground, "current_ground", pointCount),
    patchworkGround: mask(source.patchwork_ground, "patchwork_ground", pointCount),
    currentDisagreement: mask(
      source.current_disagreement,
      "current_disagreement",
      pointCount,
    ),
    patchworkDisagreement: mask(
      source.patchwork_disagreement,
      "patchwork_disagreement",
      pointCount,
    ),
  };
}

function reviewFrameMetrics(value: unknown, field: string): GroundReviewFrameMetrics {
  const source = record(value, field);
  return {
    groundIou: fraction(source.ground_iou, `${field}.ground_iou`),
    naturalGroundRecall: fraction(
      source.natural_ground_recall,
      `${field}.natural_ground_recall`,
    ),
    obstacleNonGroundRecall: fraction(
      source.obstacle_non_ground_recall,
      `${field}.obstacle_non_ground_recall`,
    ),
    latencyMs: number(source.latency_ms, `${field}.latency_ms`),
  };
}

export function decodeGroundReview(payload: unknown): GroundReview {
  const source = record(payload, "ground review");
  if (
    source.schema_version !== "missioncore.polygon-ground-review/v2"
    || source.access !== "read-only"
  ) {
    throw new GroundQualificationContractError("Неизвестная схема покадрового просмотра.");
  }
  const frameCount = integer(source.frame_count, "frame_count", 1);
  const frames = array(source.frames, "frames", 2_000).map(
    (value, index): GroundReviewFrameSummary => {
      const frame = record(value, `frames[${index}]`);
      const sequence = integer(frame.sequence, `frames[${index}].sequence`);
      if (sequence !== index) {
        throw new GroundQualificationContractError("Кадры просмотра идут не по порядку.");
      }
      const frameId = id(frame.frame_id, `frames[${index}].frame_id`);
      const frameIdentity = parseGooseFrameId(frameId);
      const datasetSequenceId = id(
        frame.dataset_sequence_id,
        `frames[${index}].dataset_sequence_id`,
      );
      const datasetFrameNumber = integer(
        frame.dataset_frame_number,
        `frames[${index}].dataset_frame_number`,
      );
      const sensorTimestampNs = text(
        frame.sensor_timestamp_ns,
        `frames[${index}].sensor_timestamp_ns`,
        20,
      );
      if (
        datasetSequenceId !== frameIdentity.datasetSequenceId
        || datasetFrameNumber !== frameIdentity.datasetFrameNumber
        || sensorTimestampNs !== frameIdentity.sensorTimestampNs
      ) {
        throw new GroundQualificationContractError(
          `frames[${index}] противоречит идентичности GOOSE-кадра.`,
        );
      }
      return {
        sequence,
        frameId,
        datasetSequenceId,
        datasetFrameNumber,
        sensorTimestampNs,
        sourcePointCount: integer(
          frame.source_point_count,
          `frames[${index}].source_point_count`,
          1,
        ),
        pointCount: integer(frame.point_count, `frames[${index}].point_count`, 1),
        current: reviewFrameMetrics(frame.current, `frames[${index}].current`),
        patchwork: reviewFrameMetrics(
          frame.patchworkpp,
          `frames[${index}].patchworkpp`,
        ),
        groundIouDelta: number(
          frame.ground_iou_delta,
          `frames[${index}].ground_iou_delta`,
          -1,
        ),
      };
    },
  );
  if (frames.length !== frameCount) {
    throw new GroundQualificationContractError("Индекс просмотра неполный.");
  }
  frames.forEach((frame, index) => {
    if (frame.pointCount > frame.sourcePointCount) {
      throw new GroundQualificationContractError(
        `frames[${index}] содержит больше preview-точек, чем source-точек.`,
      );
    }
  });
  const identitySha256 = text(source.identity_sha256, "identity_sha256", 64);
  if (!SHA256.test(identitySha256)) {
    throw new GroundQualificationContractError("Review identity должен содержать SHA-256.");
  }
  return {
    runId: id(source.run_id, "run_id"),
    identitySha256,
    sourceId: text(source.source_id, "source_id"),
    frameCount,
    previewPoints: integer(source.preview_points, "preview_points", 1),
    frames,
  };
}

export interface GooseFrameIdentity {
  datasetSequenceId: string;
  datasetFrameNumber: number;
  sensorTimestampNs: string;
}

export function parseGooseFrameId(frameId: string): GooseFrameIdentity {
  const match = GOOSE_FRAME_ID.exec(frameId);
  if (!match) {
    throw new GroundQualificationContractError(
      "frame_id не содержит GOOSE sequence/frame/timestamp.",
    );
  }
  return {
    datasetSequenceId: match[1],
    datasetFrameNumber: Number(match[2]),
    sensorTimestampNs: match[3],
  };
}

export function decodeGroundReviewFrame(payload: unknown): GroundReviewFrame {
  const source = record(payload, "ground review frame");
  if (
    source.schema_version !== "missioncore.polygon-ground-review-frame/v1"
    || source.access !== "read-only"
  ) {
    throw new GroundQualificationContractError("Неизвестная схема кадра просмотра.");
  }
  const compatible = {
    ...source,
    schema_version: "missioncore.polygon-ground-failure-preview/v1",
  };
  const decoded = decodeGroundFailurePreview(compatible);
  const intensity0To255 = array(
    source.intensity_0_255,
    "intensity_0_255",
    50_000,
  ).map((value, index) => integer(value, `intensity_0_255[${index}]`));
  if (
    intensity0To255.length !== decoded.pointCount
    || intensity0To255.some((value) => value > 255)
  ) {
    throw new GroundQualificationContractError(
      "intensity_0_255 не совпадает с point_count.",
    );
  }
  return { ...decoded, intensity0To255 };
}

async function requestJson(
  url: string,
  signal: AbortSignal | undefined,
  fetcher: QualificationFetch,
): Promise<unknown> {
  let response: Response;
  try {
    response = await fetcher(url, {
      method: "GET",
      headers: { Accept: "application/json" },
      signal,
    });
  } catch (error) {
    if (error instanceof DOMException && error.name === "AbortError") throw error;
    throw new GroundQualificationApiError("Не удалось загрузить квалификацию.");
  }
  let body: unknown;
  try {
    body = await response.json();
  } catch {
    throw new GroundQualificationContractError("Polygon API вернул повреждённый JSON.");
  }
  if (!response.ok) {
    const detail = typeof body === "object" && body !== null && "detail" in body
      ? String((body as { detail: unknown }).detail)
      : `Polygon API HTTP ${response.status}.`;
    throw new GroundQualificationApiError(detail, response.status);
  }
  return body;
}

export async function fetchGroundQualification(
  runId: string,
  {
    signal,
    fetcher = globalThis.fetch,
  }: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundQualification | null> {
  if (!SAFE_ID.test(runId)) {
    throw new GroundQualificationContractError("Недопустимый run_id.");
  }
  try {
    return decodeGroundQualification(
      await requestJson(
        `/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification`,
        signal,
        fetcher,
      ),
    );
  } catch (error) {
    if (error instanceof GroundQualificationApiError && error.status === 404) return null;
    throw error;
  }
}

export async function fetchGroundFailurePreview(
  runId: string,
  frameId: string,
  {
    signal,
    fetcher = globalThis.fetch,
  }: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundFailurePreview> {
  if (!SAFE_ID.test(runId) || !SAFE_ID.test(frameId)) {
    throw new GroundQualificationContractError("Недопустимый идентификатор preview.");
  }
  return decodeGroundFailurePreview(
    await requestJson(
      `/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification/failures/`
      + encodeURIComponent(frameId),
      signal,
      fetcher,
    ),
  );
}

export async function fetchGroundReview(
  runId: string,
  {
    signal,
    fetcher = globalThis.fetch,
  }: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundReview | null> {
  if (!SAFE_ID.test(runId)) {
    throw new GroundQualificationContractError("Недопустимый run_id.");
  }
  try {
    return decodeGroundReview(
      await requestJson(
        `/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification/review`,
        signal,
        fetcher,
      ),
    );
  } catch (error) {
    if (error instanceof GroundQualificationApiError && error.status === 404) return null;
    throw error;
  }
}

export async function fetchGroundReviewFrame(
  runId: string,
  frameId: string,
  {
    signal,
    fetcher = globalThis.fetch,
  }: { signal?: AbortSignal; fetcher?: QualificationFetch } = {},
): Promise<GroundReviewFrame> {
  if (!SAFE_ID.test(runId) || !SAFE_ID.test(frameId)) {
    throw new GroundQualificationContractError("Недопустимый идентификатор кадра.");
  }
  return decodeGroundReviewFrame(
    await requestJson(
      `/api/v1/polygon/runs/${encodeURIComponent(runId)}/qualification/review/frames/`
      + encodeURIComponent(frameId),
      signal,
      fetcher,
    ),
  );
}
