"""Pydantic models for GIBS layer metadata, colormaps, and API responses."""

from __future__ import annotations

from pydantic import BaseModel, Field, model_validator


class BBox(BaseModel):
    """Bounding box in decimal degrees (EPSG:4326)."""

    west: float = Field(description="Western longitude (-180 to 180)")
    south: float = Field(description="Southern latitude (-90 to 90)")
    east: float = Field(description="Eastern longitude (-180 to 180)")
    north: float = Field(description="Northern latitude (-90 to 90)")

    @model_validator(mode="after")
    def _validate_ranges(self) -> BBox:
        if self.south > self.north:
            raise ValueError(f"south ({self.south}) must be <= north ({self.north})")
        if not (-90 <= self.south <= 90 and -90 <= self.north <= 90):
            raise ValueError(
                f"Latitudes must be in [-90, 90], got south={self.south}, north={self.north}"
            )
        if not (-180 <= self.west <= 180 and -180 <= self.east <= 180):
            raise ValueError(
                f"Longitudes must be in [-180, 180], got west={self.west}, east={self.east}"
            )
        return self

    @property
    def wms_bbox(self) -> str:
        """Format as WMS BBOX parameter: minx,miny,maxx,maxy."""
        return f"{self.west},{self.south},{self.east},{self.north}"

    @property
    def area_sq_deg(self) -> float:
        return abs(self.east - self.west) * abs(self.north - self.south)


class TimeDimension(BaseModel):
    """Temporal extent of a layer."""

    default: str | None = None
    value: str | None = None  # ISO 8601 interval like "2020-01-01/2024-12-31/P1D"
    start: str | None = None
    end: str | None = None
    period: str | None = None  # P1D, P1M, P8D, etc.


class LayerInfo(BaseModel):
    """Parsed metadata for a single GIBS visualization layer."""

    identifier: str
    title: str
    abstract: str = ""
    formats: list[str] = Field(default_factory=list)
    tile_matrix_sets: list[str] = Field(default_factory=list)
    time: TimeDimension | None = None
    bbox: BBox | None = None
    has_colormap: bool = False
    colormap_id: str | None = None
    legend_url: str | None = None
    resource_url_template: str | None = None

    # Enriched from layer-metadata JSON
    measurement: str | None = None
    instrument: str | None = None
    platform: str | None = None
    period: str | None = None  # "Daily", "Monthly", etc.
    ongoing: bool | None = None
    day_night: str | None = None
    description: str | None = None


class ColorMapEntry(BaseModel):
    """Single entry mapping an RGB color to a data value range."""

    rgb: tuple[int, int, int]
    transparent: bool = False
    nodata: bool = False
    value: str | None = None  # Interval notation: "[200.0,200.5)"
    source_value: str | None = None
    label: str | None = None
    ref: str | None = None


class LegendEntry(BaseModel):
    """Entry in a colormap legend."""

    rgb: tuple[int, int, int]
    tooltip: str = ""
    show_tick: bool = False
    show_label: bool = False
    id: str | None = None


class ColorMap(BaseModel):
    """Parsed GIBS colormap with entries and legend."""

    title: str = ""
    units: str = ""
    entries: list[ColorMapEntry] = Field(default_factory=list)
    legend: list[LegendEntry] = Field(default_factory=list)
    legend_type: str = "continuous"  # or "discrete", "classification"


class ColorMapSet(BaseModel):
    """Complete colormap document which can contain multiple ColorMap elements."""

    maps: list[ColorMap] = Field(default_factory=list)

    @property
    def data_map(self) -> ColorMap | None:
        """Return the primary data colormap (not no-data)."""
        for m in self.maps:
            if m.entries and not all(e.nodata for e in m.entries):
                return m
        return self.maps[0] if self.maps else None


class GeocodingResult(BaseModel):
    """Result from Nominatim geocoding."""

    display_name: str
    bbox: BBox
    lat: float
    lon: float
    osm_type: str = ""
    importance: float = 0.0
