from __future__ import annotations

import math


def _go_round(x: float, decimals: int = 0) -> float:
    """Round half-away-from-zero to match Go's math.Round behavior."""
    multiplier = 10**decimals
    return math.floor(x * multiplier + 0.5) / multiplier


def convert_temp(value: float | None, units: str) -> float | None:
    """Convert temperature. raw/imperial=no-op, metric=F->C."""
    if value is None:
        return None
    if units == "metric":
        return _go_round((value - 32) * 5 / 9, 1)
    return value


def convert_wind(value: int | None, units: str) -> float | int | None:
    """Convert wind speed. raw=no-op, metric=kt->m/s, imperial=kt->mph."""
    if value is None:
        return None
    if units == "metric":
        return _go_round(value * 0.514444, 1)
    if units == "imperial":
        return _go_round(value * 1.15078, 1)
    return value


def convert_pressure(value: float | None, units: str) -> float | None:
    """Convert altimeter pressure. raw/imperial=no-op, metric=inHg->hPa.

    NOTE: This is ONLY for altimeter_inhg.
    sea_level_pressure_mb is already metric and must NOT be converted.
    """
    if value is None:
        return None
    if units == "metric":
        return _go_round(value * 33.8639, 2)
    return value


def convert_visibility(value: float | None, units: str) -> float | None:
    """Convert visibility. raw/imperial=no-op, metric=mi->km."""
    if value is None:
        return None
    if units == "metric":
        return _go_round(value * 1.60934, 2)
    return value


def convert_precipitation(value: float | None, units: str) -> float | None:
    """Convert precipitation. raw/imperial=no-op, metric=in->mm."""
    if value is None:
        return None
    if units == "metric":
        return _go_round(value * 25.4, 1)
    return value


# August-Roche-Magnus approximation (Alduchov & Eskridge 1996)
_MAGNUS_A = 17.625
_MAGNUS_B = 243.04  # °C


def compute_relative_humidity(
    temp_c: float | None, dewp_c: float | None
) -> float | None:
    """Compute RH from temp and dewpoint (Celsius) via Magnus formula.

    Returns value in [0, 100] rounded to 1 decimal, or None if inputs invalid.
    """
    if temp_c is None or dewp_c is None:
        return None
    try:
        t = float(temp_c)
        td = float(dewp_c)
        if not (math.isfinite(t) and math.isfinite(td)):
            return None
        rh = 100.0 * math.exp(
            (_MAGNUS_A * td) / (_MAGNUS_B + td)
        ) / math.exp(
            (_MAGNUS_A * t) / (_MAGNUS_B + t)
        )
        return _go_round(max(0.0, min(rh, 100.0)), 1)
    except (ValueError, TypeError, OverflowError, ZeroDivisionError):
        return None


def convert_observation(obs: Observation, units: str) -> Observation:
    """Apply all unit conversions to create a new Observation.

    Does NOT convert sea_level_pressure_mb (already metric).
    """
    from therminal.models.observation import Observation

    if units == "raw":
        return obs

    return Observation(
        station_code=obs.station_code,
        observed_at=obs.observed_at,
        observation_type=obs.observation_type,
        source=obs.source,
        temp_f=convert_temp(obs.temp_f, units),
        dewpoint_f=convert_temp(obs.dewpoint_f, units),
        relative_humidity=obs.relative_humidity,
        wind_dir_degrees=obs.wind_dir_degrees,
        wind_speed_kt=convert_wind(obs.wind_speed_kt, units),
        wind_gust_kt=convert_wind(obs.wind_gust_kt, units),
        altimeter_inhg=convert_pressure(obs.altimeter_inhg, units),
        sea_level_pressure_mb=obs.sea_level_pressure_mb,
        sky_cover_1=obs.sky_cover_1,
        sky_base_1_ft=obs.sky_base_1_ft,
        sky_cover_2=obs.sky_cover_2,
        sky_base_2_ft=obs.sky_base_2_ft,
        sky_cover_3=obs.sky_cover_3,
        sky_base_3_ft=obs.sky_base_3_ft,
        sky_cover_4=obs.sky_cover_4,
        sky_base_4_ft=obs.sky_base_4_ft,
        visibility_miles=convert_visibility(obs.visibility_miles, units),
        weather_codes=obs.weather_codes,
        precip_1hr_inches=convert_precipitation(obs.precip_1hr_inches, units),
        peak_wind_gust_kt=convert_wind(obs.peak_wind_gust_kt, units),
        peak_wind_dir=obs.peak_wind_dir,
        peak_wind_time=obs.peak_wind_time,
        feels_like_f=convert_temp(obs.feels_like_f, units),
        snow_depth_inches=convert_precipitation(obs.snow_depth_inches, units),
        raw_metar=obs.raw_metar,
    )
