from __future__ import annotations

from pathlib import Path

import numpy as np
import pytest

from k1link.compute.inline_temporal import (
    StreamingSemanticStabilizer,
    TemporalStabilizer,
    read_inline_profile,
    stabilize_world_state,
)


def _profile() -> dict[str, object]:
    root = Path(__file__).resolve().parents[1]
    profile, digest = read_inline_profile(
        root / "experiments" / "perception" / "e23_inline_temporal_profile.json"
    )
    assert len(digest) == 64
    return profile


def _object(track_id: int, box: list[float]) -> dict[str, object]:
    return {
        "association_group": "vehicle",
        "bbox_xyxy": box,
        "candidate_projected_points": 20,
        "clustered_points": 14,
        "completion_fraction": 0.9,
        "cuboid_center_map": [10.0, 2.0, 0.8],
        "cuboid_half_size": [2.25, 0.925, 0.775],
        "cuboid_quaternion_xyzw": [0.0, 0.0, 0.0, 1.0],
        "cuboid_status": "accepted-class-prior-amodal-v1",
        "distance_median_m": 10.2,
        "distance_p10_m": 9.8,
        "distance_smoothed_m": 10.1,
        "geometry": "class-prior-completed-from-visible-lidar-support",
        "ground_rejected_points": 0,
        "ground_z_map": 0.0,
        "label": "car",
        "observed_cuboid_center_map": [10.0, 2.0, 0.8],
        "observed_cuboid_half_size": [2.25, 0.925, 0.775],
        "observed_cuboid_quaternion_xyzw": [0.0, 0.0, 0.0, 1.0],
        "orientation_source": "support-pca",
        "pre_ground_clustered_points": 14,
        "score": 0.9,
        "semantic_compatible_points": 18,
        "support_coverage_fraction": 0.8,
        "support_ground_z_map": None,
        "temporal_status": "confirmed",
        "track_id": track_id,
    }


def test_e23_profile_pins_inline_stage_and_has_no_control_authority() -> None:
    profile = _profile()

    assert profile["stage"] == "warm-worker-after-fusion-before-result-publication"
    assert profile["bounds"]["maximum_track_states"] == 128
    assert profile["bounds"]["semantic_history_masks"] == 1
    assert profile["authority"] == {
        "commands_enabled": False,
        "navigation_or_safety_accepted": False,
    }


def test_e23_inline_tracking_is_bounded_and_updates_world_state() -> None:
    stabilizer = TemporalStabilizer(_profile())
    first = stabilizer.update(
        frame_index=0,
        session_seconds=0.0,
        objects=[_object(10, [100.0, 100.0, 200.0, 200.0])],
    )
    second = stabilizer.update(
        frame_index=1,
        session_seconds=0.1,
        objects=[_object(11, [102.0, 100.0, 202.0, 200.0])],
    )
    world = stabilize_world_state(
        {
            "objects": [
                {
                    "track_id": 11,
                    "velocity_status": "observed",
                }
            ],
            "delivery": {},
        },
        second,
        {},
    )

    assert first[0]["track_id"] == 10
    assert second[0]["track_id"] == 10
    assert second[0]["cuboid_status"] == "accepted-temporally-stabilized-e23-v1"
    assert world["delivery"]["temporal_stability"] == "e23-inline-bounded"
    assert world["objects"][0]["track_id"] == 10
    assert stabilizer.snapshot()["peak_track_states"] <= 128


def test_e23_semantic_state_keeps_only_one_mask_and_rejects_islands() -> None:
    stabilizer = StreamingSemanticStabilizer(_profile())
    first = np.zeros((600, 800), dtype=np.uint8)
    second = first.copy()
    second[20, 20] = 4
    second[100:110, 100:110] = 7

    stabilizer.update(first)
    output = stabilizer.update(second)
    snapshot = stabilizer.snapshot()

    assert output[20, 20] == 0
    assert np.all(output[102:108, 102:108] == 7)
    assert snapshot["history_masks"] == 1
    assert snapshot["frames"] == 2
    assert snapshot["unsupported_change_reduction_fraction"] > 0


def test_e23_profile_rejects_non_object_json(tmp_path: Path) -> None:
    profile_path = tmp_path / "profile.json"
    profile_path.write_text("[]", encoding="utf-8")

    with pytest.raises(RuntimeError, match="profile is invalid"):
        read_inline_profile(profile_path)
