"""Pydantic request/response models for accounts, subscriptions, notifications.

The User* schemas are fastapi-users' base schemas extended with display_name; the
Subscription*/Notification* models are the app's first request bodies and shape the
JSON the frontend sends and receives.
"""
import uuid
from typing import Literal

from fastapi_users import schemas
from pydantic import BaseModel, Field, field_validator

from data import grading

# Canonical metric keys a subscription may watch (kept in lockstep with grading).
ALLOWED_METRICS = tuple(grading.CLIMO_METRICS)
DEFAULT_METRICS = ["tmax", "feels", "precip"]


# --- users (fastapi-users) ---------------------------------------------------
class UserRead(schemas.BaseUser[uuid.UUID]):
    display_name: str | None = None
    # Present so the frontend can show linked/unlinked state; set via the OAuth
    # flow (discord_link.py), not editable through the profile.
    discord_id: str | None = None
    discord_dm: bool = False


class UserCreate(schemas.BaseUserCreate):
    display_name: str | None = None


class UserUpdate(schemas.BaseUserUpdate):
    display_name: str | None = None


# --- subscriptions -----------------------------------------------------------
def _check_metrics(v: list[str]) -> list[str]:
    if not v:
        raise ValueError("pick at least one metric")
    bad = [m for m in v if m not in ALLOWED_METRICS]
    if bad:
        raise ValueError(f"unknown metric(s): {', '.join(bad)}")
    # de-dupe, preserve order
    seen, out = set(), []
    for m in v:
        if m not in seen:
            seen.add(m)
            out.append(m)
    return out


class SubscriptionIn(BaseModel):
    lat: float = Field(ge=-90, le=90)
    lon: float = Field(ge=-180, le=180)
    label: str | None = None
    threshold: int = Field(ge=95, le=99)
    metrics: list[str] = Field(default_factory=lambda: list(DEFAULT_METRICS))
    kind: Literal["observed", "forecast"] = "observed"
    two_sided: bool = True

    @field_validator("metrics")
    @classmethod
    def _metrics(cls, v):
        return _check_metrics(v)


class SubscriptionPatch(BaseModel):
    threshold: int | None = Field(default=None, ge=95, le=99)
    metrics: list[str] | None = None
    two_sided: bool | None = None
    active: bool | None = None

    @field_validator("metrics")
    @classmethod
    def _metrics(cls, v):
        return _check_metrics(v) if v is not None else v


class SubscriptionOut(BaseModel):
    id: int
    cell_id: str
    label: str | None
    lat: float
    lon: float
    threshold: int
    metrics: list[str]
    kind: str
    two_sided: bool
    active: bool
    last_notified_at: float | None
    created_at: float

    model_config = {"from_attributes": True}


# --- notifications -----------------------------------------------------------
class NotificationOut(BaseModel):
    id: int
    subscription_id: int
    event_date: str
    metric: str
    direction: str
    kind: str
    percentile: float
    value: float | None
    grade: str | None
    title: str
    body: str | None
    created_at: float
    read_at: float | None

    model_config = {"from_attributes": True}


class NotificationList(BaseModel):
    notifications: list[NotificationOut]
    unread_count: int


# --- web push ----------------------------------------------------------------
class PushKeys(BaseModel):
    p256dh: str
    auth: str


class PushSubscriptionIn(BaseModel):
    """Matches the browser's PushSubscription.toJSON() shape."""
    endpoint: str
    keys: PushKeys


class PushUnsubscribeIn(BaseModel):
    endpoint: str
