"""Real-time METAR observations direct from AWC."""

from __future__ import annotations

import time
from typing import Any

import httpx

from therminal._convert import convert_observation
from therminal._version import __version__
from therminal.config import TherminalConfig
from therminal.exceptions import RateLimitError, ServerError, TherminalError
from therminal.models import Observation
from therminal.weather._awc import awc_to_observation, station_to_icao

AWC_BASE = "https://aviationweather.gov/api/data/metar"


class WeatherLive:
    """Real-time METAR observations direct from AWC.

    Fetches live weather data from aviationweather.gov, returning Observation
    objects identical in schema to WeatherHistory.observations().
    """

    def __init__(self, config: TherminalConfig | None = None) -> None:
        self._config = config or TherminalConfig()
        self._client = httpx.Client(
            timeout=self._config.live_timeout,
            headers={"User-Agent": f"therminal-py/{__version__}"},
        )

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> WeatherLive:
        return self

    def __exit__(self, *a: Any) -> None:
        self.close()

    # ── Public methods ───────────────────────────────────────

    def current(
        self,
        station: str | list[str] | None = None,
        *,
        units: str | None = None,
        tz: str | None = None,
        as_dataframe: bool = False,
    ) -> list[Observation] | Any:
        """Get latest METAR observation(s).

        Args:
            station: Station code(s). "NYC" or ["NYC", "ATL", "MDW"].
                     Batch: multiple stations -> ONE AWC request.
                     If None, uses config default.
            units: Unit system override (raw/metric/imperial).
            tz: Timezone override (reserved for future use).
            as_dataframe: Return as Pandas DataFrame.
        """
        stations, resolved = self._resolve(station, units=units, tz=tz)
        obs = self._fetch(stations, hours=1, units=resolved["units"])
        if as_dataframe:
            return _obs_to_dataframe(obs)
        return obs

    def latest(
        self,
        station: str | list[str] | None = None,
        *,
        hours: int = 1,
        units: str | None = None,
        tz: str | None = None,
        as_dataframe: bool = False,
    ) -> list[Observation] | Any:
        """Get recent METAR observations for the last N hours.

        Args:
            station: Station code(s).
            hours: Hours of history (1-360, default 1).
            units: Unit system override.
            tz: Timezone override.
            as_dataframe: Return as Pandas DataFrame.
        """
        stations, resolved = self._resolve(station, units=units, tz=tz)
        clamped_hours = min(max(hours, 1), 360)
        obs = self._fetch(stations, hours=clamped_hours, units=resolved["units"])
        if as_dataframe:
            return _obs_to_dataframe(obs)
        return obs

    # ── Internal ─────────────────────────────────────────────

    def _resolve(
        self,
        station: str | list[str] | None,
        *,
        units: str | None = None,
        tz: str | None = None,
    ) -> tuple[list[str], dict[str, str | None]]:
        """Normalize station + resolve config defaults in one call."""
        resolved = self._config.resolve(
            units=units, tz=tz,
            station=station if isinstance(station, str) else None,
        )

        if isinstance(station, list):
            return list(station), resolved

        effective = station if station is not None else resolved["station"]
        if effective is None:
            raise TherminalError("No station specified and no default in config")

        return [effective], resolved

    def _fetch(
        self,
        stations: list[str],
        hours: int,
        units: str | None,
    ) -> list[Observation]:
        """Fetch from AWC and map to Observations."""
        icao_codes = [station_to_icao(s) for s in stations]
        ids = ",".join(icao_codes)
        url = f"{AWC_BASE}?ids={ids}&format=json&hours={hours}"

        resp = self._request_with_retry(url)

        # Parse response
        try:
            raw_json = resp.json()
        except Exception:
            return []

        if not isinstance(raw_json, list):
            return []

        # Filter to requested stations and map to Observations
        station_set = set(stations)
        observations: list[Observation] = []
        for item in raw_json:
            if not isinstance(item, dict):
                continue
            obs = awc_to_observation(item)
            if obs is not None and obs.station_code in station_set:
                observations.append(obs)

        # Apply unit conversion if not raw
        if units and units != "raw":
            observations = [convert_observation(obs, units) for obs in observations]

        return observations

    def _request_with_retry(self, url: str) -> httpx.Response:
        """Make GET request with retry logic for rate limits and server errors."""
        attempts = 0
        max_retries = self._config.max_retries

        while True:
            try:
                resp = self._client.get(url)
            except httpx.TimeoutException:
                raise TherminalError("AWC request timed out") from None
            except httpx.HTTPError as exc:
                raise TherminalError(f"AWC request failed: {exc}") from exc

            if resp.status_code == 429:
                retry_after = int(resp.headers.get("Retry-After", "60"))
                if attempts < max_retries:
                    attempts += 1
                    time.sleep(min(retry_after, 10))
                    continue
                raise RateLimitError(retry_after=retry_after)

            if resp.status_code >= 500:
                if attempts < max_retries:
                    attempts += 1
                    time.sleep(2 ** attempts)
                    continue
                body = resp.text[:200]
                raise ServerError(
                    f"AWC API error {resp.status_code}: {body}",
                    status_code=resp.status_code,
                )

            if not resp.is_success:
                raise TherminalError(
                    f"AWC API error {resp.status_code}: {resp.text[:200]}"
                )

            return resp


def _obs_to_dataframe(obs: list[Observation]) -> Any:
    """Convert Observation list to DataFrame with datetime index."""
    from therminal._http import to_dataframe

    return to_dataframe([o.to_dict() for o in obs], index_col="observed_at")
