"""Tests for WeatherLive using mocked httpx responses."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import httpx
import pytest

from therminal._convert import convert_temp, convert_wind
from therminal.config import TherminalConfig
from therminal.exceptions import RateLimitError, ServerError, TherminalError
from therminal.models.observation import Observation
from therminal.weather.live import WeatherLive

AWC_MOCK = [
    {
        "icaoId": "KNYC",
        "obsTime": 1774821060,
        "temp": 10.6,
        "dewp": -1.1,
        "wdir": 140,
        "wspd": 6,
        "wgst": 15,
        "visib": "10+",
        "altim": 1029.9,
        "slp": 1029,
        "metarType": "METAR",
        "rawOb": "METAR KNYC test",
        "clouds": [{"cover": "FEW", "base": 12000}],
    }
]

AWC_MOCK_ATL = [
    {
        "icaoId": "KATL",
        "obsTime": 1774821060,
        "temp": 15.0,
        "dewp": 5.0,
        "wdir": 200,
        "wspd": 10,
        "wgst": None,
        "visib": "10+",
        "altim": 1015.0,
        "slp": 1013,
        "metarType": "METAR",
        "rawOb": "METAR KATL test",
        "clouds": [{"cover": "SCT", "base": 8000}],
    }
]


def _mock_response(
    json_data=None,
    status_code: int = 200,
    headers: dict | None = None,
    text: str = "",
) -> httpx.Response:
    """Build a mock httpx.Response."""
    resp = MagicMock(spec=httpx.Response)
    resp.status_code = status_code
    resp.is_success = 200 <= status_code < 300
    resp.headers = headers or {}
    resp.text = text
    if json_data is not None:
        resp.json.return_value = json_data
    else:
        resp.json.side_effect = ValueError("No JSON")
    return resp


# ── current() basic ────────────────────────────────────────


def test_current_single_station():
    mock_resp = _mock_response(json_data=AWC_MOCK)
    with patch.object(httpx.Client, "get", return_value=mock_resp) as mock_get:
        client = WeatherLive()
        result = client.current("NYC")
        client.close()

    assert len(result) == 1
    assert isinstance(result[0], Observation)
    assert result[0].station_code == "NYC"

    call_url = mock_get.call_args[0][0]
    assert "ids=KNYC" in call_url


def test_current_batch_stations():
    combined = AWC_MOCK + AWC_MOCK_ATL
    mock_resp = _mock_response(json_data=combined)
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        client = WeatherLive()
        result = client.current(["NYC", "ATL"])
        client.close()

    codes = {obs.station_code for obs in result}
    assert codes == {"NYC", "ATL"}
    assert len(result) == 2


def test_current_no_station_no_default():
    client = WeatherLive()
    with pytest.raises(TherminalError, match="No station specified"):
        client.current()
    client.close()


def test_current_empty_response():
    mock_resp = _mock_response(json_data=[])
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        client = WeatherLive()
        result = client.current("NYC")
        client.close()

    assert result == []


# ── Error handling ─────────────────────────────────────────


def test_rate_limit_error():
    mock_resp = _mock_response(
        status_code=429,
        headers={"Retry-After": "30"},
    )
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        client = WeatherLive(config=TherminalConfig(max_retries=0))
        with pytest.raises(RateLimitError) as exc_info:
            client.current("NYC")
        client.close()

    assert exc_info.value.retry_after == 30


def test_server_error_after_retries():
    mock_resp = _mock_response(
        status_code=500,
        text="Internal Server Error",
    )
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        client = WeatherLive(config=TherminalConfig(max_retries=0))
        with pytest.raises(ServerError) as exc_info:
            client.current("NYC")
        client.close()

    assert exc_info.value.status_code == 500


# ── Config defaults ────────────────────────────────────────


def test_config_default_station():
    mock_resp = _mock_response(json_data=AWC_MOCK_ATL)
    with patch.object(httpx.Client, "get", return_value=mock_resp) as mock_get:
        cfg = TherminalConfig(station="ATL")
        client = WeatherLive(config=cfg)
        result = client.current()
        client.close()

    call_url = mock_get.call_args[0][0]
    assert "ids=KATL" in call_url
    assert len(result) == 1
    assert result[0].station_code == "ATL"


# ── Units conversion ──────────────────────────────────────


def test_current_metric_conversion():
    mock_resp = _mock_response(json_data=AWC_MOCK)
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        client = WeatherLive()
        result = client.current("NYC", units="metric")
        client.close()

    obs = result[0]
    # AWC temp 10.6C -> F = 51.1 -> metric conversion back to C = 10.6
    # The AWC data goes C->F via awc_to_observation, then convert_observation F->C
    raw_temp_f = 51.1  # celsius_to_f(10.6) = 51.1
    expected_temp = convert_temp(raw_temp_f, "metric")
    assert obs.temp_f == expected_temp

    expected_wind = convert_wind(6, "metric")
    assert obs.wind_speed_kt == expected_wind


# ── latest() ──────────────────────────────────────────────


def test_latest_passes_hours():
    mock_resp = _mock_response(json_data=AWC_MOCK)
    with patch.object(httpx.Client, "get", return_value=mock_resp) as mock_get:
        client = WeatherLive()
        client.latest("NYC", hours=3)
        client.close()

    call_url = mock_get.call_args[0][0]
    assert "hours=3" in call_url


def test_latest_clamps_hours():
    mock_resp = _mock_response(json_data=AWC_MOCK)
    with patch.object(httpx.Client, "get", return_value=mock_resp) as mock_get:
        client = WeatherLive()
        client.latest("NYC", hours=999)
        client.close()

    call_url = mock_get.call_args[0][0]
    assert "hours=360" in call_url


# ── Context manager ───────────────────────────────────────


def test_context_manager():
    mock_resp = _mock_response(json_data=AWC_MOCK)
    with patch.object(httpx.Client, "get", return_value=mock_resp):
        with WeatherLive() as client:
            result = client.current("NYC")

    assert len(result) == 1


class TestAsDataFrame:
    def test_current_as_dataframe(self):
        mock_resp = _mock_response(json_data=AWC_MOCK)
        with patch.object(httpx.Client, "get", return_value=mock_resp):
            client = WeatherLive()
            df = client.current("NYC", as_dataframe=True)

        import pandas as pd

        assert isinstance(df, pd.DataFrame)
        assert df.index.name == "observed_at"
        assert "temp_f" in df.columns
        assert len(df) == 1

    def test_latest_as_dataframe(self):
        mock_resp = _mock_response(json_data=AWC_MOCK * 3)
        with patch.object(httpx.Client, "get", return_value=mock_resp):
            client = WeatherLive()
            df = client.latest("NYC", hours=3, as_dataframe=True)

        import pandas as pd

        assert isinstance(df, pd.DataFrame)
        assert len(df) == 3
