from __future__ import annotations

from collections.abc import Callable
from pathlib import Path
from typing import Any

import pytest
from fastapi import APIRouter, HTTPException
from fastapi.routing import APIRoute

from k1link.simulation import (
    AuthorityProfile,
    ProviderPin,
    QualificationArtifact,
    QualificationRun,
    QualificationRunStore,
    ReproducibilityTier,
    RunKind,
    RunState,
)
from k1link.web.polygon_api import build_polygon_router

SHA_A = "a" * 64
SHA_B = "b" * 64
SHA_C = "c" * 64


def _run(run_id: str = "s1b-ui0-001") -> QualificationRun:
    return QualificationRun(
        run_id=run_id,
        episode_id=f"episode-{run_id}",
        kind=RunKind.SIMULATION_CLOSED_LOOP,
        state=RunState.ADMITTED,
        scenario_generation="px4-v1.17.0-stock-rover-ackermann",
        scenario_sha256=SHA_A,
        profile_generation="stock-rover-lifecycle-v1",
        profile_sha256=SHA_B,
        mission_core_commit="6cb1495a1234567890abcdef1234567890abcdef",
        providers=(
            ProviderPin(
                identifier="px4-autopilot",
                version="v1.17.0",
                revision="v1.17.0",
            ),
            ProviderPin(
                identifier="gazebo",
                version="harmonic",
                revision="8.9.0",
            ),
        ),
        host_profile_id="mission-gpu-s0",
        host_profile_sha256=SHA_C,
        seed=42,
        reproducibility_tier=ReproducibilityTier.R1,
        authority=AuthorityProfile(
            generation=1,
            command_ttl_max_ns=250_000_000,
            heartbeat_timeout_monotonic_ns=500_000_000,
        ),
        clock_domain="gazebo:/clock",
        created_at_utc="2026-07-24T15:35:00Z",
    )


def _accepted_run(root: Path) -> QualificationRun:
    store = QualificationRunStore(root)
    admitted = store.create(_run())
    starting = store.transition(
        admitted.run_id,
        RunState.STARTING,
        expected_revision=0,
        observed_at_utc="2026-07-24T15:35:01Z",
        host_monotonic_ns=1,
    )
    running = store.transition(
        admitted.run_id,
        RunState.RUNNING,
        expected_revision=starting.revision,
        observed_at_utc="2026-07-24T15:35:02Z",
        host_monotonic_ns=2,
        sim_time_ns=0,
    )
    event = store.append_event(
        admitted.run_id,
        event_type="orchestrator.providers-started",
        observed_at_utc="2026-07-24T15:35:03Z",
        host_monotonic_ns=3,
        sim_time_ns=1_000_000,
        payload={"provider_ids": ["micro-xrce-dds-agent", "px4-gazebo-stock-rover"]},
        expected_revision=running.revision,
    )
    store.register_artifact(
        admitted.run_id,
        QualificationArtifact(
            artifact_id="provider-log-index",
            kind="log-index",
            relative_path="provider-runtime/processes.json",
            sha256=SHA_A,
            byte_length=512,
            source_of_record=True,
        ),
    )
    stopping = store.transition(
        admitted.run_id,
        RunState.STOPPING,
        expected_revision=event.sequence,
        observed_at_utc="2026-07-24T15:35:04Z",
        host_monotonic_ns=4,
        sim_time_ns=2_000_000,
    )
    return store.transition(
        admitted.run_id,
        RunState.COMPLETED,
        expected_revision=stopping.revision,
        observed_at_utc="2026-07-24T15:35:05Z",
        host_monotonic_ns=5,
        sim_time_ns=2_000_000,
        reason="operator-stop-clean",
    )


def _endpoint(router: APIRouter, path: str, method: str) -> Callable[..., Any]:
    for route in router.routes:
        if isinstance(route, APIRoute) and route.path == path and method in route.methods:
            return route.endpoint
    raise AssertionError(f"{method} {path} route is missing")


def test_polygon_api_is_read_only_and_fails_closed_when_unconfigured() -> None:
    router = build_polygon_router(root_provider=lambda: None)
    routes = [route for route in router.routes if isinstance(route, APIRoute)]

    assert all(route.methods <= {"GET", "HEAD"} for route in routes)
    with pytest.raises(HTTPException) as failure:
        _endpoint(router, "/api/v1/polygon/runs", "GET")(limit=20)
    assert failure.value.status_code == 503
    assert "не настроен" in failure.value.detail


def test_polygon_api_publishes_bounded_path_free_qualification_evidence(
    tmp_path: Path,
) -> None:
    root = tmp_path / "runs"
    accepted = _accepted_run(root)
    router = build_polygon_router(root_provider=lambda: root)

    catalog = _endpoint(router, "/api/v1/polygon/runs", "GET")(limit=20)
    detail = _endpoint(router, "/api/v1/polygon/runs/{run_id}", "GET")(
        run_id=accepted.run_id,
        event_limit=2,
    )

    assert catalog["schema_version"] == "missioncore.polygon-run-catalog/v1"
    assert catalog["access"] == "read-only"
    assert catalog["total"] == 1
    assert catalog["items"][0]["state"] == "completed"
    assert catalog["items"][0]["terminal_reason"] == "operator-stop-clean"
    assert detail["schema_version"] == "missioncore.polygon-run-detail/v1"
    assert detail["access"] == "read-only"
    assert detail["run"]["run_id"] == accepted.run_id
    assert detail["run"]["authority"]["actuator_authority"] is False
    assert detail["commands"] == {"count": 0, "content_exposed": False}
    assert detail["events_total"] == accepted.revision
    assert detail["events_truncated"] is True
    assert len(detail["events"]) == 2
    assert detail["artifacts"][0]["relative_path"] == "provider-runtime/processes.json"
    serialized = repr({"catalog": catalog, "detail": detail})
    assert str(tmp_path) not in serialized
    assert "command_ttl_max_ns" in serialized
    assert all("http" not in artifact for artifact in detail["artifacts"])


def test_polygon_api_rejects_corrupt_evidence_without_partial_response(
    tmp_path: Path,
) -> None:
    root = tmp_path / "runs"
    accepted = _accepted_run(root)
    event_path = root / accepted.run_id / "events" / "00000000000000000001.json"
    event_path.write_text("{broken", encoding="utf-8")
    router = build_polygon_router(root_provider=lambda: root)

    with pytest.raises(HTTPException) as failure:
        _endpoint(router, "/api/v1/polygon/runs", "GET")(limit=20)
    assert failure.value.status_code == 500
    assert "целостности" in failure.value.detail
