"""K1-native observation archive and Rerun preparation adapter."""

from __future__ import annotations

import stat
import threading
from collections.abc import Mapping
from pathlib import Path

from k1link.device_plugins.xgrids_k1.archive import (
    LegacySessionCandidate,
    discover_legacy_viewer_sessions,
)
from k1link.device_plugins.xgrids_k1.recorded_point_colors import (
    RecordedPointColorOverlayStore,
)
from k1link.device_plugins.xgrids_k1.rrd_export import (
    RrdExportCancelled,
    RrdExportError,
    export_k1mqtt_to_rrd,
)
from k1link.sessions.active import recover_stale_active_session_marker
from k1link.sessions.models import (
    ObservationArtifactCandidate,
    ObservationSessionCandidate,
    SessionIntegrityError,
    SessionSource,
)
from k1link.sessions.plugin_contract import (
    ObservationArchiveSource,
    ObservationRuntimeContribution,
    PluginRecordingExportCancelled,
    PluginRecordingExportError,
)
from k1link.sessions.store import resolve_missioncore_evidence_dir
from k1link.web.camera_archive import recover_incomplete_camera_archives

XGRIDS_K1_PLUGIN_ID = "nodedc.device.xgrids-lixelkity-k1"


def build_xgrids_k1_observation(repository_root: Path) -> ObservationRuntimeContribution:
    """Compose every K1 evidence root behind the generic observation ABI."""

    roots = (
        ("xgrids-k1.viewer-live.repository", repository_root.resolve() / "sessions"),
        (
            "xgrids-k1.viewer-live.evidence",
            resolve_missioncore_evidence_dir(repository_root),
        ),
    )
    point_colors = RecordedPointColorOverlayStore()
    return ObservationRuntimeContribution(
        archives=tuple(
            ObservationArchiveSource(
                plugin_id=XGRIDS_K1_PLUGIN_ID,
                archive_id=archive_id,
                root=root,
                discover=_discover_archive,
                recover=_recover_archive,
            )
            for archive_id, root in roots
        ),
        recording_exporter=_export_recording,
        point_color_renderer=point_colors.render,
    )


def xgrids_k1_archive_source(
    root: Path,
    *,
    archive_id: str = "xgrids-k1.viewer-live",
) -> ObservationArchiveSource:
    """Build one explicit archive source for tests, tools, or migration jobs."""

    return ObservationArchiveSource(
        plugin_id=XGRIDS_K1_PLUGIN_ID,
        archive_id=archive_id,
        root=root,
        discover=_discover_archive,
        recover=_recover_archive,
    )


def _recover_archive(root: Path) -> None:
    recover_stale_active_session_marker(root)
    recover_incomplete_camera_archives(root)


def _discover_archive(root: Path) -> tuple[ObservationSessionCandidate, ...]:
    return tuple(
        _to_host_candidate(candidate) for candidate in discover_legacy_viewer_sessions(root)
    )


def _to_host_candidate(candidate: LegacySessionCandidate) -> ObservationSessionCandidate:
    session_id = candidate.session_id
    raw_path = candidate.raw_path
    raw_bytes = candidate.raw_byte_length
    replay_raw_bytes = candidate.replay_raw_byte_length
    replay_metadata_bytes = candidate.replay_metadata_byte_length
    replay_capture_clock_origin_bytes = candidate.replay_capture_clock_origin_byte_length
    replay_capture_clock_bytes = candidate.replay_capture_clock_byte_length
    replayable = candidate.replayable
    metadata_path = raw_path.with_name("mqtt.metadata.jsonl")
    capture_clock_origin_path = raw_path.with_name("mqtt.timeline.origin.json")
    capture_clock_path = candidate.capture_clock_path

    artifacts = [
        ObservationArtifactCandidate(
            artifact_id="raw-transport-primary",
            kind="raw-transport",
            media_type="application/x-nodedc-k1mqtt",
            locator=raw_path,
            byte_length=raw_bytes,
            replay_byte_length=replay_raw_bytes if replayable else 0,
            sha256=candidate.raw_sha256,
            integrity_status=candidate.raw_integrity_status,
        )
    ]
    if replay_metadata_bytes > 0:
        artifacts.append(
            ObservationArtifactCandidate(
                artifact_id="raw-transport-index",
                kind="raw-transport-index",
                media_type="application/x-ndjson",
                locator=metadata_path,
                byte_length=_regular_file_size(metadata_path),
                replay_byte_length=replay_metadata_bytes if replayable else 0,
                sha256=None,
                integrity_status=candidate.raw_integrity_status,
            )
        )
    if replay_capture_clock_bytes > 0:
        if capture_clock_path is None:
            raise SessionIntegrityError("K1 capture clock locator is unavailable")
        artifacts.append(
            ObservationArtifactCandidate(
                artifact_id="raw-transport-clock",
                kind="raw-transport-clock",
                media_type="application/vnd.nodedc.capture-clock+json",
                locator=capture_clock_path,
                byte_length=replay_capture_clock_bytes,
                replay_byte_length=replay_capture_clock_bytes if replayable else 0,
                sha256=candidate.capture_clock_sha256,
                integrity_status="verified",
            )
        )
    if replay_capture_clock_origin_bytes > 0:
        artifacts.append(
            ObservationArtifactCandidate(
                artifact_id="raw-transport-clock-origin",
                kind="raw-transport-clock-origin",
                media_type="application/vnd.nodedc.capture-clock-origin+json",
                locator=capture_clock_origin_path,
                byte_length=replay_capture_clock_origin_bytes,
                replay_byte_length=(replay_capture_clock_origin_bytes if replayable else 0),
                sha256=candidate.capture_clock_origin_sha256,
                integrity_status="verified",
            )
        )

    media_sources = candidate.media_sources
    artifacts.extend(
        ObservationArtifactCandidate(
            artifact_id=media.artifact_id,
            kind="recorded-video",
            media_type="video/mp4",
            locator=media.locator,
            byte_length=media.byte_length,
            replay_byte_length=0,
            sha256=None,
            integrity_status="validated-structure",
        )
        for media in media_sources
    )

    sources: list[SessionSource] = []
    modalities = candidate.modalities
    if "point-cloud" in modalities:
        sources.append(
            SessionSource(
                source_id="sensor.lidar.primary",
                semantic_channel_id="spatial.point-cloud.recorded",
                modality="point-cloud",
                status="recorded",
                seekable=replayable,
                artifact_id="raw-transport-primary",
            )
        )
    if "trajectory" in modalities:
        sources.append(
            SessionSource(
                source_id="spatial.trajectory",
                semantic_channel_id="spatial.pose.recorded",
                modality="trajectory",
                status="recorded",
                seekable=replayable,
                artifact_id="raw-transport-primary",
            )
        )
    sources.extend(
        SessionSource(
            source_id=media.source_id,
            semantic_channel_id="camera.video.recorded",
            modality="video",
            status="recorded",
            seekable=True,
            artifact_id=media.artifact_id,
        )
        for media in media_sources
    )

    return ObservationSessionCandidate(
        session_id=session_id,
        display_name=candidate.display_name,
        status=candidate.status,
        started_at_utc=candidate.started_at_utc,
        completed_at_utc=candidate.completed_at_utc,
        duration_seconds=candidate.duration_seconds,
        modalities=modalities,
        replayable=replayable,
        total_bytes=candidate.total_bytes,
        allowed_root=candidate.allowed_root,
        session_root=candidate.session_root,
        primary_replay_artifact_id="raw-transport-primary" if replayable else None,
        timeline_origin_epoch_ns=(candidate.timeline_origin_epoch_ns if replayable else None),
        timeline_origin_monotonic_ns=(
            candidate.timeline_origin_monotonic_ns if replayable else None
        ),
        sources=tuple(sources),
        artifacts=tuple(artifacts),
    )


def _regular_file_size(path: Path) -> int:
    try:
        value = path.lstat()
    except OSError as exc:
        raise SessionIntegrityError("K1 transport index is unavailable") from exc
    if not stat.S_ISREG(value.st_mode) or stat.S_ISLNK(value.st_mode):
        raise SessionIntegrityError("K1 transport index is not a regular file")
    return value.st_size


def _export_recording(
    source: Path,
    destination: Path,
    *,
    artifacts: Mapping[str, Path] | None = None,
    cancel_event: threading.Event | None = None,
    activity_callback: object | None = None,
) -> dict[str, object]:
    try:
        return dict(
            export_k1mqtt_to_rrd(
                source,
                destination,
                capture_clock_path=(
                    None if artifacts is None else artifacts.get("raw-transport-clock")
                ),
                capture_clock_origin_path=(
                    None if artifacts is None else artifacts.get("raw-transport-clock-origin")
                ),
                cancel_event=cancel_event,
                activity_callback=activity_callback if callable(activity_callback) else None,
            )
        )
    except RrdExportCancelled as exc:
        raise PluginRecordingExportCancelled("K1 recording export was cancelled") from exc
    except RrdExportError as exc:
        raise PluginRecordingExportError("K1 recording export failed") from exc
