"""Validation for immutable LAB E8 source-paced tracking results."""

from __future__ import annotations

import hashlib
import json
import math
import os
import re
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Any, TypeGuard

from k1link.sessions import SessionIntegrityError

from .jobs import CameraComputeJob, validate_camera_compute_job

RESULT_SCHEMA = "missioncore.e8-realtime-tracking-result/v1"
IDENTITY_SCHEMA = "missioncore.e8-realtime-tracking-identity/v1"
REPORT_SCHEMA = "missioncore.e8-realtime-tracking-report/v1"
FRAME_SCHEMA = "missioncore.e8-realtime-tracking-frame/v1"
TELEMETRY_SCHEMA = "missioncore.e8-realtime-tracking-telemetry/v1"
PROFILE_SCHEMA = "missioncore.e8-realtime-tracking-profile/v1"
MAX_JSON_BYTES = 64 * 1024 * 1024
MAX_LINE_BYTES = 2 * 1024 * 1024
MAX_SAFE_INTEGER = (1 << 53) - 1

_SAFE_RESULT_ID = re.compile(r"^e8-realtime-tracking-[a-f0-9]{64}$")
_SHA256 = re.compile(r"^[a-f0-9]{64}$")


@dataclass(frozen=True, slots=True)
class RealtimeTrackingArtifact:
    kind: str
    path: Path
    media_type: str
    byte_length: int
    sha256: str
    schema_version: str | None


@dataclass(frozen=True, slots=True)
class RealtimeTrackingQualificationResult:
    result_id: str
    result_root: Path
    job: CameraComputeJob
    mode: str
    expected_frame_count: int
    processed_frame_count: int
    dropped_frame_count: int
    accepted: bool
    artifacts: tuple[RealtimeTrackingArtifact, ...]

    def artifact(self, kind: str) -> RealtimeTrackingArtifact:
        matches = tuple(item for item in self.artifacts if item.kind == kind)
        if len(matches) != 1:
            raise SessionIntegrityError(f"realtime tracking artifact is unavailable: {kind}")
        return matches[0]


def validate_realtime_tracking_qualification_result(
    job_root: Path,
    result_root: Path,
) -> RealtimeTrackingQualificationResult:
    job = validate_camera_compute_job(job_root)
    root = result_root.expanduser().resolve(strict=True)
    if not root.is_dir() or _SAFE_RESULT_ID.fullmatch(root.name) is None:
        raise SessionIntegrityError("realtime tracking result root is invalid")
    result = _read_object(root / "result.json", root)
    identity = result.get("identity")
    identity_sha256 = result.get("identity_sha256")
    if (
        result.get("schema_version") != RESULT_SCHEMA
        or result.get("result_id") != root.name
        or not isinstance(identity, dict)
        or identity.get("schema_version") != IDENTITY_SCHEMA
        or not isinstance(identity_sha256, str)
        or _SHA256.fullmatch(identity_sha256) is None
        or root.name != f"e8-realtime-tracking-{identity_sha256}"
        or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
        or identity.get("job_id") != job.job_id
        or identity.get("input_sha256") != job.input_sha256
        or identity.get("session_id") != job.session_id
        or identity.get("source_id") != job.source_id
        or result.get("ground_truth") is not False
        or result.get("publication_scope") != "recorded-realtime-qualification-only"
        or result.get("acceptance_state") not in {"accepted", "rejected"}
    ):
        raise SessionIntegrityError("realtime tracking identity is inconsistent")

    selection = identity.get("selection")
    configuration = identity.get("configuration")
    if not isinstance(selection, dict) or not isinstance(configuration, dict):
        raise SessionIntegrityError("realtime tracking selection is unavailable")
    expected = selection.get("frame_count")
    source_start = selection.get("source_start_frame_index")
    source_end = selection.get("source_end_frame_index")
    timeline_start = selection.get("timeline_start_seconds")
    timeline_end = selection.get("timeline_end_seconds")
    profile = configuration.get("profile")
    if (
        not isinstance(expected, int)
        or isinstance(expected, bool)
        or expected < 2
        or not isinstance(source_start, int)
        or isinstance(source_start, bool)
        or not isinstance(source_end, int)
        or isinstance(source_end, bool)
        or source_start < 0
        or source_end - source_start + 1 != expected
        or source_end >= job.segment_count
        or not _finite_number(timeline_start)
        or not _finite_number(timeline_end)
        or float(timeline_start) < job.timeline_start_seconds
        or float(timeline_end) > job.timeline_end_seconds
        or float(timeline_end) <= float(timeline_start)
        or not isinstance(profile, dict)
        or profile.get("schema_version") != PROFILE_SCHEMA
    ):
        raise SessionIntegrityError("realtime tracking selection is invalid")
    mode = profile.get("mode")
    realtime = profile.get("realtime")
    if (
        mode not in {"pilot", "qualification", "overload-negative-control"}
        or not isinstance(realtime, dict)
        or realtime.get("queue_policy") != "bounded-latest-wins"
        or not isinstance(realtime.get("queue_capacity"), int)
        or not 1 <= int(realtime["queue_capacity"]) <= 8
    ):
        raise SessionIntegrityError("realtime tracking profile is invalid")

    artifacts = _validate_artifacts(root, result.get("artifacts"))
    report = _read_object(_artifact(artifacts, "realtime-tracking-report").path, root)
    metrics = report.get("metrics")
    acceptance = report.get("acceptance")
    if (
        report.get("schema_version") != REPORT_SCHEMA
        or report.get("result_id") != root.name
        or report.get("identity") != identity
        or report.get("ground_truth") is not False
        or report.get("state") != result.get("acceptance_state")
        or not isinstance(metrics, dict)
        or not isinstance(acceptance, dict)
        or not isinstance(acceptance.get("accepted"), bool)
        or acceptance.get("navigation_or_safety_accepted") is not False
        or (result.get("acceptance_state") == "accepted") != acceptance["accepted"]
    ):
        raise SessionIntegrityError("realtime tracking report is inconsistent")
    processed = metrics.get("frames_processed")
    dropped = metrics.get("frames_dropped")
    queue = metrics.get("queue")
    health = metrics.get("health_counts")
    if (
        metrics.get("frames_expected") != expected
        or metrics.get("frames_published") != expected
        or not isinstance(processed, int)
        or isinstance(processed, bool)
        or not 1 <= processed <= expected
        or not isinstance(dropped, int)
        or isinstance(dropped, bool)
        or dropped < 0
        or processed + dropped != expected
        or result.get("frames_processed") != processed
        or metrics.get("frames_failed") != 0
        or not isinstance(queue, dict)
        or queue.get("capacity") != realtime["queue_capacity"]
        or queue.get("published") != expected
        or queue.get("consumed") != processed
        or queue.get("dropped_overflow") != dropped
        or queue.get("final_depth") != 0
        or not isinstance(queue.get("maximum_depth"), int)
        or not 0 <= queue["maximum_depth"] <= queue["capacity"]
        or queue.get("closed") is not True
        or not isinstance(health, dict)
        or sum(health.values()) != processed
    ):
        raise SessionIntegrityError("realtime tracking accounting is inconsistent")

    indices = _validate_realtime_frames(
        _artifact(artifacts, "realtime-tracking-frames").path,
        root,
        expected_count=processed,
        selection_count=expected,
        source_start=source_start,
        timeline_start=float(timeline_start),
        timeline_end=float(timeline_end),
    )
    _validate_realtime_telemetry(
        _artifact(artifacts, "realtime-tracking-telemetry").path,
        root,
        expected_indices=indices,
    )
    return RealtimeTrackingQualificationResult(
        result_id=root.name,
        result_root=root,
        job=job,
        mode=str(mode),
        expected_frame_count=expected,
        processed_frame_count=processed,
        dropped_frame_count=dropped,
        accepted=bool(acceptance["accepted"]),
        artifacts=artifacts,
    )


def _validate_realtime_frames(
    path: Path,
    root: Path,
    *,
    expected_count: int,
    selection_count: int,
    source_start: int,
    timeline_start: float,
    timeline_end: float,
) -> tuple[int, ...]:
    _confined_regular_file(path, root)
    indices: list[int] = []
    previous_time = -math.inf
    try:
        with path.open("rb") as stream:
            for line in stream:
                if not line or len(line) > MAX_LINE_BYTES:
                    raise SessionIntegrityError("realtime tracking frame line is outside bounds")
                value = json.loads(line)
                frame_index = value.get("frame_index") if isinstance(value, dict) else None
                session_seconds = value.get("session_seconds") if isinstance(value, dict) else None
                detections = value.get("detections") if isinstance(value, dict) else None
                tracks = value.get("tracks") if isinstance(value, dict) else None
                delivery = value.get("delivery") if isinstance(value, dict) else None
                if (
                    not isinstance(value, dict)
                    or value.get("schema_version") != FRAME_SCHEMA
                    or not isinstance(frame_index, int)
                    or isinstance(frame_index, bool)
                    or not 0 <= frame_index < selection_count
                    or (indices and frame_index <= indices[-1])
                    or value.get("sequence") != frame_index + 1
                    or value.get("source_frame_index") != source_start + frame_index
                    or value.get("source_sequence") != source_start + frame_index + 1
                    or not _finite_number(session_seconds)
                    or not timeline_start - 0.001 <= float(session_seconds) <= timeline_end + 0.001
                    or float(session_seconds) <= previous_time
                    or not isinstance(detections, list)
                    or not isinstance(tracks, list)
                    or not isinstance(delivery, dict)
                    or delivery.get("health") not in {"healthy", "degraded", "stale", "unavailable"}
                    or not _finite_number(delivery.get("result_age_ms"))
                    or float(delivery["result_age_ms"]) < 0
                ):
                    raise SessionIntegrityError("realtime tracking frame metadata is inconsistent")
                _validate_observations(detections, tracks)
                indices.append(frame_index)
                previous_time = float(session_seconds)
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise SessionIntegrityError("realtime tracking frames are unavailable") from exc
    if len(indices) != expected_count:
        raise SessionIntegrityError("realtime tracking frame count changed")
    return tuple(indices)


def _validate_realtime_telemetry(
    path: Path,
    root: Path,
    *,
    expected_indices: tuple[int, ...],
) -> None:
    _confined_regular_file(path, root)
    indices: list[int] = []
    try:
        with path.open("rb") as stream:
            for line in stream:
                if not line or len(line) > MAX_LINE_BYTES:
                    raise SessionIntegrityError("realtime telemetry line is outside bounds")
                value = json.loads(line)
                if (
                    not isinstance(value, dict)
                    or value.get("schema_version") != TELEMETRY_SCHEMA
                    or not isinstance(value.get("frame_index"), int)
                    or value.get("health") not in {"healthy", "degraded", "stale", "unavailable"}
                    or not _finite_number(value.get("result_age_ms"))
                    or float(value["result_age_ms"]) < 0
                    or not _finite_number(value.get("processing_ms"))
                    or float(value["processing_ms"]) < 0
                ):
                    raise SessionIntegrityError("realtime tracking telemetry is inconsistent")
                indices.append(int(value["frame_index"]))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise SessionIntegrityError("realtime tracking telemetry is unavailable") from exc
    if tuple(indices) != expected_indices:
        raise SessionIntegrityError("realtime tracking telemetry binding changed")


def _validate_observations(detections: list[object], tracks: list[object]) -> None:
    for detection in detections:
        if not isinstance(detection, dict):
            raise SessionIntegrityError("realtime tracking detection is invalid")
        _validate_box(detection.get("bbox_xyxy"))
        if (
            not isinstance(detection.get("class_id"), int)
            or not isinstance(detection.get("label"), str)
            or not _finite_number(detection.get("score"))
            or not 0 <= float(detection["score"]) <= 1
        ):
            raise SessionIntegrityError("realtime tracking detection fields are invalid")
    track_ids: set[int] = set()
    for track in tracks:
        if not isinstance(track, dict):
            raise SessionIntegrityError("realtime tracking observation is invalid")
        _validate_box(track.get("bbox_xyxy"))
        track_id = track.get("track_id")
        if (
            not isinstance(track_id, int)
            or isinstance(track_id, bool)
            or track_id < 1
            or track_id in track_ids
            or not isinstance(track.get("label"), str)
            or not _finite_number(track.get("score"))
            or not isinstance(track.get("hits"), int)
            or track["hits"] < 1
        ):
            raise SessionIntegrityError("realtime tracking observation fields are invalid")
        track_ids.add(track_id)


def _validate_box(value: object) -> None:
    if (
        not isinstance(value, list)
        or len(value) != 4
        or not all(_finite_number(item) for item in value)
        or not 0 <= float(value[0]) < float(value[2]) <= 800
        or not 0 <= float(value[1]) < float(value[3]) <= 600
    ):
        raise SessionIntegrityError("realtime tracking box is invalid")


def _validate_artifacts(
    root: Path,
    raw: object,
) -> tuple[RealtimeTrackingArtifact, ...]:
    expected = {
        "realtime-tracking-frames": (
            "frames.jsonl",
            "application/x-ndjson",
            FRAME_SCHEMA,
        ),
        "realtime-tracking-telemetry": (
            "telemetry.jsonl",
            "application/x-ndjson",
            TELEMETRY_SCHEMA,
        ),
        "worker-gpu-telemetry": ("gpu-telemetry.jsonl", "application/x-ndjson", None),
        "realtime-tracking-report": (
            "run-report.json",
            "application/json",
            REPORT_SCHEMA,
        ),
    }
    if not isinstance(raw, list) or len(raw) != len(expected):
        raise SessionIntegrityError("realtime tracking artifact set is invalid")
    artifacts: list[RealtimeTrackingArtifact] = []
    seen: set[str] = set()
    for value in raw:
        if not isinstance(value, dict):
            raise SessionIntegrityError("realtime tracking artifact descriptor is invalid")
        kind = value.get("kind")
        if not isinstance(kind, str) or kind in seen or kind not in expected:
            raise SessionIntegrityError("realtime tracking artifact kind is invalid")
        seen.add(kind)
        expected_path, expected_media_type, expected_schema = expected[kind]
        path = root / expected_path
        metadata = _confined_regular_file(path, root)
        byte_length = value.get("byte_length")
        digest = value.get("sha256")
        if (
            value.get("path") != expected_path
            or value.get("media_type") != expected_media_type
            or value.get("schema_version") != expected_schema
            or not isinstance(byte_length, int)
            or isinstance(byte_length, bool)
            or not 0 < byte_length <= MAX_SAFE_INTEGER
            or metadata.st_size != byte_length
            or not isinstance(digest, str)
            or _SHA256.fullmatch(digest) is None
            or _sha256_file(path) != digest
        ):
            raise SessionIntegrityError("realtime tracking artifact identity changed")
        artifacts.append(
            RealtimeTrackingArtifact(
                kind=kind,
                path=path,
                media_type=expected_media_type,
                byte_length=byte_length,
                sha256=digest,
                schema_version=expected_schema,
            )
        )
    return tuple(artifacts)


def _artifact(
    artifacts: tuple[RealtimeTrackingArtifact, ...],
    kind: str,
) -> RealtimeTrackingArtifact:
    matches = tuple(item for item in artifacts if item.kind == kind)
    if len(matches) != 1:
        raise SessionIntegrityError(f"realtime tracking artifact is unavailable: {kind}")
    return matches[0]


def _read_object(path: Path, root: Path) -> dict[str, Any]:
    metadata = _confined_regular_file(path, root)
    if not 0 < metadata.st_size <= MAX_JSON_BYTES:
        raise SessionIntegrityError("realtime tracking JSON is outside bounds")
    try:
        value = json.loads(path.read_text(encoding="utf-8-sig"))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise SessionIntegrityError("realtime tracking JSON is unavailable") from exc
    if not isinstance(value, dict):
        raise SessionIntegrityError("realtime tracking JSON root is invalid")
    return value


def _confined_regular_file(path: Path, root: Path) -> os.stat_result:
    try:
        resolved_root = root.resolve(strict=True)
        resolved = path.resolve(strict=True)
        metadata = path.lstat()
    except OSError as exc:
        raise SessionIntegrityError("realtime tracking artifact is unavailable") from exc
    if (
        stat.S_ISLNK(metadata.st_mode)
        or not stat.S_ISREG(metadata.st_mode)
        or not resolved.is_relative_to(resolved_root)
    ):
        raise SessionIntegrityError("realtime tracking artifact is not confined")
    return metadata


def _sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        while chunk := stream.read(1024 * 1024):
            digest.update(chunk)
    return digest.hexdigest()


def _canonical_json(value: object) -> bytes:
    try:
        return json.dumps(
            value,
            ensure_ascii=False,
            sort_keys=True,
            separators=(",", ":"),
            allow_nan=False,
        ).encode("utf-8")
    except (TypeError, ValueError) as exc:
        raise SessionIntegrityError("realtime tracking identity is not canonical JSON") from exc


def _finite_number(value: object) -> TypeGuard[int | float]:
    return (
        isinstance(value, (int, float))
        and not isinstance(value, bool)
        and math.isfinite(float(value))
    )
