from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from kindle_dashboard.weather import Forecast

RAIN_THRESHOLD_MM = 0.2
NO_RAIN_LABEL = "No rain in 7d"


def build_next_rain_header_message(
    forecast: Forecast,
    now: datetime,
    *,
    rain_threshold_mm: float = RAIN_THRESHOLD_MM,
) -> str | None:
    next_window = build_next_hourly_rain_window(
        forecast,
        now,
        rain_threshold_mm=rain_threshold_mm,
    )
    if next_window is not None:
        start_time, end_time = next_window
        start_label = (
            "now" if start_time <= now < end_time else start_time.strftime("%H:%M")
        )
        return f"Next Rain {start_label}-{end_time.strftime('%H:%M')}"

    next_day = build_next_rain_day(forecast, now, rain_threshold_mm=rain_threshold_mm)
    if next_day is None:
        return NO_RAIN_LABEL

    today = now.date()
    if next_day == today:
        return "Next Rain today"
    if next_day == (today + timedelta(days=1)):
        return "Next Rain tomorrow"
    return f"Next Rain {next_day.strftime('%a')}"


def build_next_hourly_rain_window(
    forecast: Forecast,
    now: datetime,
    *,
    rain_threshold_mm: float,
) -> tuple[datetime, datetime] | None:
    rainy_intervals: list[tuple[datetime, datetime]] = []

    for interval_end, precipitation_value in zip(
        forecast.hourly_datetimes,
        forecast.precipitation,
        strict=False,
    ):
        if float(precipitation_value) < rain_threshold_mm:
            continue

        if interval_end <= now:
            continue

        interval_start = interval_end - timedelta(hours=1)
        rainy_intervals.append((interval_start, interval_end))

    if not rainy_intervals:
        return None

    window_start, window_end = rainy_intervals[0]
    for interval_start, interval_end in rainy_intervals[1:]:
        if interval_start > window_end:
            break
        window_end = max(window_end, interval_end)

    return window_start, window_end


def build_next_rain_day(
    forecast: Forecast,
    now: datetime,
    *,
    rain_threshold_mm: float,
) -> date | None:
    for day, precipitation_sum in zip(
        forecast.daily_dates,
        forecast.daily_precipitation_sum,
        strict=False,
    ):
        if precipitation_sum < rain_threshold_mm:
            continue
        if day >= now.date():
            return day

    return None


def build_sun_event_markers(
    forecast: Forecast,
    window_start: datetime,
    window_end: datetime,
) -> list[tuple[datetime, bool]]:
    markers = [
        (sunrise, True)
        for sunrise in forecast.daily_sunrise_datetimes
        if window_start <= sunrise <= window_end
    ]
    markers.extend(
        (sunset, False)
        for sunset in forecast.daily_sunset_datetimes
        if window_start <= sunset <= window_end
    )

    markers.sort(key=lambda marker: marker[0])
    return markers


def build_hourly_series(
    forecast: Forecast,
    values: list[float],
    hour_slots: list[datetime],
    *,
    hour_shift: int,
) -> list[float]:
    lookup: dict[datetime, float] = {}

    for timestamp, value in zip(forecast.hourly_datetimes, values, strict=False):
        shifted_timestamp = timestamp + timedelta(hours=hour_shift)
        key = shifted_timestamp.replace(minute=0, second=0, microsecond=0)
        lookup[key] = float(value)

    if not lookup:
        message = "Forecast series did not provide any hourly values"
        raise ValueError(message)

    known_slots = sorted(lookup)
    return [
        lookup[slot]
        if slot in lookup
        else lookup[
            min(
                known_slots,
                key=lambda known_slot: abs((known_slot - slot).total_seconds()),
            )
        ]
        for slot in hour_slots
    ]


def normalise_chart_now(now: datetime, forecast: Forecast) -> datetime:
    return now.astimezone(forecast.forecast_timezone).replace(tzinfo=None)
