"""Pravidla pro GET /sites/{id}/notifications (ceny, EV, predikce záporných cen)."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime, time, timedelta, timezone
from decimal import Decimal
from typing import Any, Literal
from zoneinfo import ZoneInfo

PRAGUE = ZoneInfo("Europe/Prague")

NotificationLevel = Literal["success", "info", "warning", "error"]
NotificationAction = Literal["connect_ev", "replan", "import_prices", "switch_auto"]


@dataclass(frozen=True)
class PriceSlot:
    interval_start: datetime
    buy: float
    sell: float


@dataclass(frozen=True)
class EvSessionRow:
    id: int
    charger_id: int
    energy_delivered_wh: float
    target_soc_pct: float | None
    session_start: datetime
    battery_capacity_kwh: float | None
    make: str | None
    model: str | None
    default_target_soc_pct: float | None
    charger_code: str
    soc_at_connect_pct: float | None


@dataclass(frozen=True)
class NegWindowRow:
    predicted_date: date
    window_start_hour: int
    window_end_hour: int
    probability_pct: int


def _num(v: Any) -> float | None:
    if v is None:
        return None
    if isinstance(v, Decimal):
        return float(v)
    return float(v)


def _ev_connect_hint(ev_sessions: list[EvSessionRow], current_price: float, avg_buy: float | None) -> str:
    if ev_sessions:
        return ""
    kwh = 30.0
    if current_price < 0:
        return f"Připojíš-li Teslu, dostaneš zaplaceno za ~{kwh * abs(current_price):.0f} Kč za 30 kWh."
    if avg_buy is None:
        return ""
    savings = avg_buy - current_price
    return f"Připojíš-li Teslu, ušetříš ~{kwh * max(0, savings):.0f} Kč."


def _estimate_ev_soc(s: EvSessionRow) -> float:
    cap = s.battery_capacity_kwh
    delivered_kwh = (s.energy_delivered_wh or 0) / 1000.0
    if s.soc_at_connect_pct is not None and cap and cap > 0:
        return min(95.0, float(s.soc_at_connect_pct) + (delivered_kwh / cap) * 100.0)
    if cap and cap > 0 and s.energy_delivered_wh:
        return min(95.0, (delivered_kwh / cap) * 100.0 + 20.0)
    return 50.0


def _estimate_needed_kwh(soc_pct: float, battery_kwh: float, ev_sessions: list[EvSessionRow]) -> float:
    bat_needed = max(0.0, (80.0 - soc_pct) / 100.0 * battery_kwh)
    ev_needed = 0.0
    for s in ev_sessions:
        cap = s.battery_capacity_kwh or 60.0
        tgt = (s.target_soc_pct if s.target_soc_pct is not None else None) or (
            s.default_target_soc_pct if s.default_target_soc_pct is not None else 80.0
        )
        delivered = (s.energy_delivered_wh or 0) / 1000.0
        want = max(0.0, (tgt / 100.0) * cap - delivered)
        ev_needed += want
    return bat_needed + ev_needed


def _estimate_ev_free_kwh(s: EvSessionRow) -> float:
    cap = s.battery_capacity_kwh or 60.0
    delivered = (s.energy_delivered_wh or 0) / 1000.0
    return max(0.0, cap * 0.95 - delivered)


def _tesla_potential_kwh(ev_sessions: list[EvSessionRow]) -> float:
    """Odhad „kolik lze ještě dobít“ pro typickou Teslu, pokud není připojena."""
    if ev_sessions:
        return 0.0
    return 55.0


def _date_label(d: date) -> str:
    today = datetime.now(PRAGUE).date()
    if d == today:
        return "dnes"
    if d == today + timedelta(days=1):
        return "zítra"
    return f"{d.day}. {d.month}."


def _hours_until(predicted_date: date, start_hour: int) -> float:
    start_local = datetime.combine(predicted_date, time(hour=start_hour, minute=0), tzinfo=PRAGUE)
    now = datetime.now(PRAGUE)
    delta = start_local - now
    return max(0.0, delta.total_seconds() / 3600.0)


def build_smart_notifications(
    *,
    prices: list[PriceSlot],
    avg_buy: float | None,
    soc_pct: float | None,
    battery_kwh: float | None,
    ev_sessions: list[EvSessionRow],
    neg_windows: list[NegWindowRow],
    mode: str,
    sell_price_now: float | None,
) -> list[dict[str, Any]]:
    notifications: list[dict[str, Any]] = []

    soc = float(soc_pct) if soc_pct is not None else 50.0
    bat_kwh = float(battery_kwh) if battery_kwh is not None and battery_kwh > 0 else 10.0

    current_buy = prices[0].buy if prices else None
    current_sell = prices[0].sell if prices else None

    # 1. Záporná cena právě teď
    if current_buy is not None and current_buy < 0:
        bat_free_kwh = (100.0 - soc) / 100.0 * bat_kwh
        ev_hint = _ev_connect_hint(ev_sessions, current_buy, avg_buy)
        notifications.append(
            {
                "id": "neg_price_now",
                "level": "success",
                "title": f"Záporné ceny právě teď ({current_buy:.3f} Kč/kWh)",
                "body": (
                    f"Dostaneš zaplaceno za každý odebraný kWh. "
                    f"Baterie může pojmout ještě {bat_free_kwh:.1f} kWh. "
                    + ev_hint
                ),
                "eta_minutes": 0,
                "action": "connect_ev" if not ev_sessions else None,
            }
        )

    # 2. Levná cena v příštích 6 h
    avg_ok = avg_buy is not None and avg_buy > 0
    if prices and avg_ok and not (current_buy is not None and current_buy < 0):
        horizon = prices[:24]
        cheap_slots = [p for p in horizon if p.buy < avg_buy * 0.60]
        if cheap_slots:
            cheapest = min(cheap_slots, key=lambda p: p.buy)
            now_utc = datetime.now(timezone.utc)
            istart = cheapest.interval_start
            if istart.tzinfo is None:
                istart = istart.replace(tzinfo=timezone.utc)
            eta_min = int((istart - now_utc).total_seconds() / 60)
            eta_min = max(0, eta_min)
            savings_per_kwh = avg_buy - cheapest.buy

            ev_plug_useful = (not ev_sessions) or any(_estimate_ev_soc(s) < 60.0 for s in ev_sessions)
            bat_low = soc < 70.0

            if ev_plug_useful or bat_low:
                needed_kwh = _estimate_needed_kwh(soc, bat_kwh, ev_sessions)
                savings_czk = needed_kwh * max(0.0, savings_per_kwh)
                extra_body = ""
                if ev_plug_useful and not ev_sessions:
                    extra_body = " Připoj auto před tímto oknem."
                notifications.append(
                    {
                        "id": "cheap_price_soon",
                        "level": "info",
                        "title": f"Levná elektřina za {eta_min} min ({cheapest.buy:.3f} Kč/kWh)",
                        "body": (
                            f"Cena bude o {savings_per_kwh:.2f} Kč/kWh nižší než průměr. "
                            f"Potenciální úspora: ~{savings_czk:.0f} Kč."
                            + extra_body
                        ),
                        "eta_minutes": eta_min,
                        "action": "connect_ev" if ev_plug_useful and not ev_sessions else None,
                    }
                )

    # 3. Predikované záporné ceny
    for window in neg_windows[:2]:
        if any(n["id"] == "neg_price_now" for n in notifications):
            continue
        date_label = _date_label(window.predicted_date)
        window_str = f"{window.window_start_hour:02d}:00–{window.window_end_hour:02d}:00"
        hours_until = _hours_until(window.predicted_date, window.window_start_hour)
        bat_free = (100.0 - soc) / 100.0 * bat_kwh
        ev_free = sum(_estimate_ev_free_kwh(s) for s in ev_sessions)
        total_free = bat_free + ev_free
        tesla_kwh = _tesla_potential_kwh(ev_sessions)
        ev_hint = (
            f" Připojíš-li Teslu, lze dobít až {tesla_kwh:.0f} kWh navíc." if not ev_sessions else ""
        )
        lvl: NotificationLevel = "success" if window.probability_pct >= 70 else "info"
        notifications.append(
            {
                "id": f"neg_pred_{window.predicted_date}_{window.window_start_hour}",
                "level": lvl,
                "title": (
                    f"Záporné ceny {date_label} {window_str} ({window.probability_pct}% jistota)"
                ),
                "body": (
                    f"Solver naplánuje max. odběr ze sítě. Lze dobít ~{total_free:.0f} kWh zdarma."
                    + ev_hint
                ),
                "eta_minutes": int(hours_until * 60),
                "action": "connect_ev" if not ev_sessions else None,
            }
        )

    # 4. Manuální režim + drahá cena + plná baterie
    mode_u = (mode or "").strip().upper()
    sell = sell_price_now if sell_price_now is not None else (current_sell if current_sell is not None else None)
    if mode_u != "AUTO" and avg_ok and sell is not None and sell > avg_buy * 1.30 and soc > 70.0:
        pct_above = ((sell / avg_buy) - 1.0) * 100.0 if avg_buy else 0.0
        notifications.append(
            {
                "id": "manual_expensive",
                "level": "warning",
                "title": "Drahá elektřina – systém není v AUTO",
                "body": (
                    f"Cena prodeje {sell:.3f} Kč/kWh (+{pct_above:.0f}% nad průměr). "
                    f"Baterie {soc:.0f}%. Přepnutím na AUTO solver využije cenové okno."
                ),
                "eta_minutes": None,
                "action": "switch_auto",
            }
        )

    priority = {"error": 0, "success": 1, "warning": 2, "info": 3}
    notifications.sort(key=lambda n: priority.get(n["level"], 9))
    return notifications
