from pydantic import BaseModel, Field, model_validator
from typing import Optional, List, Literal
from datetime import datetime
from bson import ObjectId


class GeofenceShape(str):
    CIRCLE = "circle"
    POLYGON = "polygon"


class GeoJSONPoint(BaseModel):
    type: Literal["Point"]
    coordinates: List[float]  # [lng, lat]


class GeofenceBase(BaseModel):
    account_id: str
    user_id: Optional[str] = None
    name: str
    description: Optional[str] = None
    shape: Literal["circle", "polygon"]
    center: Optional[GeoJSONPoint] = None  # for circles
    radius: Optional[float] = None         # for circles
    coordinates: Optional[List[List[float]]] = None  # for polygons: list of [lng, lat]

    @model_validator(mode="after")
    def validate_shape_fields(self):
        if self.shape == "circle":
            if not self.center or self.radius is None:
                raise ValueError("Circle geofence must include both 'center' and 'radius'.")
        elif self.shape == "polygon":
            if not self.coordinates:
                raise ValueError("Polygon geofence must include 'coordinates'.")
            if not all(isinstance(c, list) and len(c) == 2 and all(isinstance(v, float) for v in c) for c in self.coordinates):
                raise ValueError("Each coordinate must be a [lng, lat] float pair.")
        return self


class GeofenceCreate(GeofenceBase):
    pass


class GeofenceUpdate(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    shape: Optional[Literal["circle", "polygon"]] = None
    center: Optional[GeoJSONPoint] = None
    radius: Optional[float] = None
    coordinates: Optional[List[List[float]]] = None

    @model_validator(mode="after")
    def validate_shape_fields(self):
        if self.shape == "circle":
            if self.center is not None and self.radius is None:
                raise ValueError("If 'shape' is 'circle', 'radius' is required with 'center'.")
        elif self.shape == "polygon":
            if self.coordinates and not all(isinstance(c, list) and len(c) == 2 and all(isinstance(v, float) for v in c) for c in self.coordinates):
                raise ValueError("Each coordinate must be a [lng, lat] float pair.")
        return self


class Geofence(GeofenceBase):
    id: Optional[str] = None
    created_date: datetime = Field(default_factory=datetime.utcnow)

    class Config:
        populate_by_name = True
        from_attributes = True
        json_encoders = {ObjectId: str}


class GeofencesList(BaseModel):
    total_count: int
    users: List[Geofence]
