"""Deterministic, browser-oriented views of authoritative analysis documents."""

from __future__ import annotations

import json
from dataclasses import asdict
from pathlib import Path
from typing import Any, Mapping

from mountain_twin.analysis_contract import MethodProvenance, ProviderProvenance

SCENARIO_TIMES = ("06:00", "09:00", "12:00", "15:00", "18:00")
EXPECTED_ROUTE_ID = "tmb_day_01"
EXPECTED_POINT_COUNT = 1515
EXPECTED_RESULT_COUNT = EXPECTED_POINT_COUNT * len(SCENARIO_TIMES)


def build_solar_visualization_document(analysis: Mapping[str, Any]) -> dict[str, Any]:
    """Make a compact visual projection without recalculating scientific results."""
    _validate_analysis(analysis)
    terrain = analysis["terrain"]
    source = ProviderProvenance(
        provider=terrain["provider"],
        product=terrain["product"],
        surface_semantics=terrain["surface_semantics"],
        native_resolution=tuple(terrain["native_resolution"]),
        horizontal_crs=terrain["horizontal_crs"],
        vertical_reference=terrain["vertical_reference"],
        limitations=tuple(analysis["limitations"]),
    )
    method = MethodProvenance(
        method=analysis["analysis_method"],
        model_version="route_solar_exposure_v0_1",
        observer_model=analysis["observer_model"],
        interpolation_method="continuous bilinear grid-post",
        range_policy=analysis["range_policy"],
        thresholds={"range_boundary_margin_m": analysis["range_boundary_margin_m"]},
        assumptions=(analysis["time_semantics"],),
    )
    grouped = _group_by_scenario(analysis["results"])
    first = grouped[analysis["scenario_datetimes"][0]]
    route_points = [
        {
            "point_index": result["point_index"],
            "latitude": result["latitude"],
            "longitude": result["longitude"],
            "route_distance_m": result["route_distance_m"],
            "route_elevation_m": result["route_elevation_m"],
        }
        for result in first
    ]
    summaries = {
        summary["scenario_datetime"]: summary for summary in analysis["scenario_summaries"]
    }
    scenarios = []
    for scenario_datetime in analysis["scenario_datetimes"]:
        rows = grouped[scenario_datetime]
        scenarios.append(
            {
                "time": scenario_datetime[11:16],
                "scenario_datetime": scenario_datetime,
                "timezone": rows[0]["timezone"],
                "summary": summaries[scenario_datetime],
                "results": [
                    {
                        "status": row["status"],
                        "solar_azimuth_deg": row["solar_azimuth_deg"],
                        "solar_elevation_deg": row["solar_elevation_deg"],
                        "horizon_angle_deg": row["horizon_angle_deg"],
                        "controlling_distance_m": row["controlling_distance_m"],
                        "final_range_m": row["horizon_range_m"],
                        "ranges_tested_m": row["ranges_tested_m"],
                        "converged_at_m": row["converged_at_m"],
                        "range_convergence_state": row["range_convergence_state"],
                        "reason_codes": row["reason_codes"],
                    }
                    for row in rows
                ],
            }
        )
    return {
        "contract_version": "mountain_twin_visualization_v0_1",
        "source_analysis": {
            "analysis_id": analysis["analysis_id"],
            "analysis_type": "solar_exposure",
            "semantic_type": analysis["semantic_type"],
            "version": "0.1",
            "time_semantics": analysis["time_semantics"],
        },
        "route": analysis["route"],
        "provider": asdict(source),
        "method": asdict(method),
        "scenarios": scenarios,
        "route_points": route_points,
    }


def write_solar_visualization_export(source: Path, destination: Path) -> dict[str, Any]:
    """Read the authoritative output and write a deterministic local UI export."""
    document = build_solar_visualization_document(json.loads(source.read_text(encoding="utf-8")))
    destination.parent.mkdir(parents=True, exist_ok=True)
    destination.write_text(
        json.dumps(document, indent=2, sort_keys=True, allow_nan=False) + "\n", encoding="utf-8"
    )
    return document


def _group_by_scenario(results: list[Mapping[str, Any]]) -> dict[str, list[Mapping[str, Any]]]:
    grouped: dict[str, list[Mapping[str, Any]]] = {}
    for row in results:
        grouped.setdefault(row["scenario_datetime"], []).append(row)
    for rows in grouped.values():
        rows.sort(key=lambda row: row["point_index"])
    return grouped


def _validate_analysis(analysis: Mapping[str, Any]) -> None:
    if analysis.get("route", {}).get("route_id") != EXPECTED_ROUTE_ID:
        raise ValueError("visual prototype requires authoritative TMB Day 1 analysis")
    if analysis.get("route", {}).get("point_count") != EXPECTED_POINT_COUNT:
        raise ValueError("visual prototype requires exactly 1,515 route points")
    if analysis.get("semantic_type") != "surface_illumination":
        raise ValueError("visual prototype supports surface_illumination only")
    if analysis.get("observer_height_m") != 0.0:
        raise ValueError("visual prototype requires zero-height surface observer")
    if len(analysis.get("results", [])) != EXPECTED_RESULT_COUNT:
        raise ValueError("visual prototype requires exactly 7,575 results")
    times = tuple(value[11:16] for value in analysis.get("scenario_datetimes", []))
    if times != SCENARIO_TIMES:
        raise ValueError("visual prototype requires the five explicit TMB Day 1 scenario times")
    if analysis.get("time_semantics") != "explicit_simultaneous_scenarios_not_gpx_or_hiking_times":
        raise ValueError("scenario time semantics must explicitly exclude GPX hiking chronology")
