#!/usr/bin/env python3
"""Verify Open-Meteo is the global MVP weather source for source-backed tiles."""

from __future__ import annotations

import json
import os
import urllib.parse
import urllib.request
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
PROVIDER_CONTRACT = ROOT / "Data" / "Weather" / "open_meteo_mvp_source.json"
TILE_MANIFEST = ROOT / "Data" / "Tiles" / "tile_weather_manifest.json"
WEATHER_SUBSYSTEM_CPP = ROOT / "Source" / "AgrarianGame" / "AgrarianWeatherProviderSubsystem.cpp"
WEATHER_SUBSYSTEM_H = ROOT / "Source" / "AgrarianGame" / "AgrarianWeatherProviderSubsystem.h"
ROADMAP = ROOT / "AGRARIAN_DEVELOPMENT_ROADMAP.md"
TDD = ROOT / "Docs" / "TechnicalDesignDocument.md"


EXPECTED_SNIPPETS = {
    WEATHER_SUBSYSTEM_H: [
        "OpenMeteoForecastEndpoint",
        "https://api.open-meteo.com/v1/forecast",
        "RequestWeatherForTile",
    ],
    WEATHER_SUBSYSTEM_CPP: [
        "current=temperature_2m,relative_humidity_2m,precipitation",
        "daily=temperature_2m_max,temperature_2m_min,weather_code,precipitation_sum,wind_speed_10m_max",
        "OutSnapshot.Provider = TEXT(\"open-meteo\");",
    ],
    ROADMAP: [
        "[x] Use Open-Meteo as the first global MVP weather source.",
    ],
    TDD: [
        "Open-Meteo is the first global MVP weather source",
        "Data/Weather/open_meteo_mvp_source.json",
    ],
}


def assert_static_contract() -> tuple[dict, dict]:
    missing = []
    for path, snippets in EXPECTED_SNIPPETS.items():
        text = path.read_text(encoding="utf-8")
        for snippet in snippets:
            if snippet not in text:
                missing.append(f"{path.relative_to(ROOT)}: {snippet}")
    if missing:
        raise RuntimeError("Open-Meteo MVP source verification failed: " + "; ".join(missing))

    provider = json.loads(PROVIDER_CONTRACT.read_text(encoding="utf-8"))
    manifest = json.loads(TILE_MANIFEST.read_text(encoding="utf-8"))
    if provider.get("provider_id") != "open-meteo":
        raise RuntimeError("Provider contract does not identify Open-Meteo")
    if provider.get("api_key_required") is not False:
        raise RuntimeError("Open-Meteo MVP source should not require an API key")
    if not manifest.get("tiles"):
        raise RuntimeError("Tile weather manifest has no source-backed weather tiles")
    for tile in manifest["tiles"]:
        if tile.get("provider") != "open-meteo":
            raise RuntimeError(f"Unexpected tile weather provider for {tile.get('tile_id')}: {tile.get('provider')}")
    return provider, manifest


def build_open_meteo_url(provider: dict, tile: dict) -> str:
    params = provider["request_parameters"]
    query = {
        "latitude": f"{float(tile['center_latitude']):.6f}",
        "longitude": f"{float(tile['center_longitude']):.6f}",
        "current": ",".join(params["current"]),
        "daily": ",".join(params["daily"]),
        "forecast_days": str(params["forecast_days"]),
        "timezone": params["timezone"],
    }
    return provider["forecast_endpoint"] + "?" + urllib.parse.urlencode(query, safe=",")


def assert_live_open_meteo_response(provider: dict, manifest: dict) -> None:
    if os.environ.get("AGRARIAN_SKIP_LIVE_WEATHER_CHECK") == "1":
        print("Skipping live Open-Meteo check because AGRARIAN_SKIP_LIVE_WEATHER_CHECK=1")
        return

    for tile in manifest["tiles"]:
        url = build_open_meteo_url(provider, tile)
        with urllib.request.urlopen(url, timeout=20) as response:
            payload = json.loads(response.read().decode("utf-8"))

        current = payload.get("current", {})
        daily = payload.get("daily", {})
        required_current = {"temperature_2m", "relative_humidity_2m", "precipitation", "weather_code", "wind_speed_10m"}
        required_daily = {"temperature_2m_max", "temperature_2m_min", "weather_code", "precipitation_sum"}
        missing_current = sorted(required_current - set(current))
        missing_daily = sorted(required_daily - set(daily))
        if missing_current or missing_daily:
            raise RuntimeError(
                f"Open-Meteo response for {tile['tile_id']} missing current={missing_current} daily={missing_daily}"
            )
        if not isinstance(daily["temperature_2m_max"], list) or not daily["temperature_2m_max"]:
            raise RuntimeError(f"Open-Meteo daily max temperature is empty for {tile['tile_id']}")


def main() -> None:
    provider, manifest = assert_static_contract()
    assert_live_open_meteo_response(provider, manifest)
    print("Open-Meteo MVP weather source verification complete.")


if __name__ == "__main__":
    main()
