#!/usr/bin/env python3
from __future__ import annotations

import argparse
import hashlib
import json
import subprocess
import tempfile
from pathlib import Path

from k1link.compute import (
    PATCHWORKPP_SOURCE_COMMIT,
    PATCHWORKPP_SOURCE_TAG,
    RAVNOVES00_CENTRAL_WINDOWS,
    E10LidarFieldSource,
    GroundBenchmarkProfile,
    LidarFieldReviewV1,
    PatchworkPPGroundSegmenter,
    build_lidar_field_review,
)


def _arguments() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description=(
            "Build accumulated RAVNOVES00 central-urban LiDAR ground review "
            "with content-bound camera context previews."
        )
    )
    parser.add_argument("source_pack", type=Path)
    parser.add_argument("camera_epoch", type=Path)
    parser.add_argument("output_root", type=Path)
    parser.add_argument("--ffmpeg", default="ffmpeg")
    parser.add_argument("--patchwork-module", default="pypatchworkpp")
    parser.add_argument("--patchwork-source-tag", default=PATCHWORKPP_SOURCE_TAG)
    parser.add_argument(
        "--patchwork-source-commit",
        default=PATCHWORKPP_SOURCE_COMMIT,
    )
    parser.add_argument("--sensor-height-m", type=float, default=1.27)
    parser.add_argument("--map-vertical-origin-offset-m", type=float, default=1.27)
    return parser.parse_args()


def _preview_segments(epoch: Path) -> dict[str, Path]:
    resolved = epoch.expanduser().resolve(strict=True)
    init = resolved / "init.mp4"
    index_path = resolved / "index.jsonl"
    if not init.is_file() or init.is_symlink() or not index_path.is_file():
        raise RuntimeError("Camera epoch is incomplete")
    required = {
        window.preview_source_frame_index + 1: window.key for window in RAVNOVES00_CENTRAL_WINDOWS
    }
    found: dict[str, Path] = {}
    with index_path.open(encoding="utf-8") as stream:
        for line in stream:
            value = json.loads(line)
            sequence = value.get("sequence") if isinstance(value, dict) else None
            if sequence not in required:
                continue
            relative = value.get("path")
            expected_sha256 = value.get("sha256")
            expected_length = value.get("length")
            if (
                value.get("schema_version") != "missioncore.camera-recording-index/v1"
                or relative != f"segments/{sequence}.m4s"
                or not isinstance(expected_sha256, str)
                or not isinstance(expected_length, int)
            ):
                raise RuntimeError("Camera preview segment index is invalid")
            segment = resolved / relative
            if (
                segment.is_symlink()
                or not segment.is_file()
                or segment.stat().st_size != expected_length
                or _sha256(segment) != expected_sha256
            ):
                raise RuntimeError("Camera preview segment failed integrity")
            found[required[sequence]] = segment
    if set(found) != set(required.values()):
        raise RuntimeError("Camera preview segments are incomplete")
    return found


def _extract_previews(
    ffmpeg: str,
    epoch: Path,
    output: Path,
) -> dict[str, Path]:
    init = epoch.expanduser().resolve(strict=True) / "init.mp4"
    segments = _preview_segments(epoch)
    previews: dict[str, Path] = {}
    for key, segment in segments.items():
        target = output / f"{key}.jpg"
        subprocess.run(
            [
                ffmpeg,
                "-hide_banner",
                "-loglevel",
                "error",
                "-i",
                f"concat:{init}|{segment}",
                "-frames:v",
                "1",
                "-vf",
                "scale=640:480",
                "-q:v",
                "3",
                str(target),
            ],
            check=True,
        )
        if not target.is_file() or target.stat().st_size < 1_000:
            raise RuntimeError("Camera preview extraction failed")
        previews[key] = target
    return previews


def main() -> int:
    arguments = _arguments()
    profile = GroundBenchmarkProfile(
        profile_id="ravnoves00-central-urban-operator-height-ground-review/v1",
        patchwork_sensor_height_proxy_m=arguments.sensor_height_m,
        patchwork_map_vertical_origin_offset_m=(arguments.map_vertical_origin_offset_m),
        patchwork_height_evidence="operator-estimated",
    )
    source = E10LidarFieldSource(arguments.source_pack)
    try:
        patchwork = PatchworkPPGroundSegmenter.load(
            profile=profile,
            module_name=arguments.patchwork_module,
            source_tag=arguments.patchwork_source_tag,
            source_commit=arguments.patchwork_source_commit,
        )
        with tempfile.TemporaryDirectory(prefix="missioncore-lidar-field-review-") as value:
            previews = _extract_previews(
                arguments.ffmpeg,
                arguments.camera_epoch,
                Path(value),
            )
            output = build_lidar_field_review(
                source,
                arguments.output_root,
                patchwork=patchwork,
                profile=profile,
                preview_paths=previews,
            )
    finally:
        source.close()
    review = LidarFieldReviewV1(output)
    try:
        print(
            json.dumps(
                {
                    "review_id": review.review_id,
                    "display_name": review.report["display_name"],
                    "session_id": review.report["session_id"],
                    "source": review.report["source"],
                    "selection": review.report["selection"],
                    "windows": review.report["windows"],
                    "metrics": review.report["metrics"],
                    "decision": review.report["decision"],
                },
                ensure_ascii=False,
                indent=2,
            )
        )
    finally:
        review.close()
    return 0


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


if __name__ == "__main__":
    raise SystemExit(main())
