import uuid
from datetime import date, datetime
from decimal import Decimal

from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator

from .models import ObservationStatus, QuoteStatus, RequestStatus, RequestType, UserRole


class PointInput(BaseModel):
    longitude: float = Field(ge=-180, le=180)
    latitude: float = Field(ge=-90, le=90)


class PhotoOut(BaseModel):
    id: uuid.UUID
    url: str
    original_name: str
    mime_type: str
    size_bytes: int


class ObservationCreate(PointInput):
    commune: str = Field(min_length=2, max_length=160)
    adresse: str | None = None
    type_observation: str = "nid_suppose"
    support: str | None = None
    hauteur_estimee: float | None = Field(default=None, ge=0, le=100)
    niveau_danger: str = "modere"
    description: str | None = None
    contact_nom: str | None = None
    contact_telephone: str | None = None
    contact_email: EmailStr | None = None

    @field_validator(
        "adresse",
        "support",
        "description",
        "contact_nom",
        "contact_telephone",
        "contact_email",
        mode="before",
    )
    @classmethod
    def blank_optional_values_to_none(cls, value):
        if isinstance(value, str) and not value.strip():
            return None
        return value


class ObservationOut(BaseModel):
    id: uuid.UUID
    reference: str
    commune: str
    adresse: str | None
    type_observation: str
    support: str | None
    hauteur_estimee: float | None
    niveau_danger: str
    description: str | None
    contact_nom: str | None = None
    contact_telephone: str | None = None
    contact_email: str | None = None
    status: ObservationStatus
    longitude: float
    latitude: float
    created_at: datetime
    updated_at: datetime
    photos: list[PhotoOut] = Field(default_factory=list)


class ObservationStatusUpdate(BaseModel):
    status: ObservationStatus


class RequestCreate(PointInput):
    observation_id: uuid.UUID | None = None
    type_demande: RequestType
    commune: str = Field(min_length=2, max_length=160)
    demandeur_nom: str = Field(min_length=2, max_length=160)
    demandeur_fonction: str | None = None
    demandeur_email: EmailStr
    demandeur_telephone: str | None = None
    adresse: str = Field(min_length=3, max_length=500)
    niveau_urgence: str = "normal"
    description_risque: str | None = None
    hauteur_estimee: float | None = Field(default=None, ge=0, le=100)
    support: str | None = None
    difficulte_acces: str | None = None
    proprietaire_nom: str | None = None
    proprietaire_adresse: str | None = None
    proprietaire_telephone: str | None = None
    proprietaire_email: EmailStr | None = None
    accord_proprietaire: bool | None = None
    justification_danger_imminent: str | None = None

    @field_validator(
        "observation_id",
        "demandeur_fonction",
        "demandeur_telephone",
        "description_risque",
        "support",
        "difficulte_acces",
        "proprietaire_nom",
        "proprietaire_adresse",
        "proprietaire_telephone",
        "proprietaire_email",
        "justification_danger_imminent",
        mode="before",
    )
    @classmethod
    def blank_optional_values_to_none(cls, value):
        if isinstance(value, str) and not value.strip():
            return None
        return value

    @model_validator(mode="after")
    def validate_conditional_fields(self):
        if self.type_demande == RequestType.prive_accord:
            if not self.proprietaire_nom or self.accord_proprietaire is not True:
                raise ValueError("Le propriétaire et son accord sont requis pour un espace privé avec accord.")
        if self.type_demande == RequestType.prive_sans_accord and not self.justification_danger_imminent:
            raise ValueError("La justification du danger grave et imminent est requise.")
        return self


class RequestOut(BaseModel):
    id: uuid.UUID
    reference: str
    observation_id: uuid.UUID | None = None
    type_demande: RequestType
    status: RequestStatus
    commune: str
    adresse: str
    niveau_urgence: str
    demandeur_nom: str | None = None
    demandeur_fonction: str | None = None
    demandeur_email: str | None = None
    demandeur_telephone: str | None = None
    description_risque: str | None
    hauteur_estimee: float | None = None
    support: str | None = None
    difficulte_acces: str | None = None
    proprietaire_nom: str | None = None
    proprietaire_adresse: str | None = None
    proprietaire_telephone: str | None = None
    proprietaire_email: str | None = None
    accord_proprietaire: bool | None = None
    justification_danger_imminent: str | None = None
    assigned_to: str | None
    intervention_date: datetime | None
    longitude: float
    latitude: float
    created_at: datetime
    updated_at: datetime
    photos: list[PhotoOut] = Field(default_factory=list)


class StatusUpdate(BaseModel):
    status: RequestStatus
    commentaire: str | None = None
    auteur: str | None = None


class TreatmentUpdate(BaseModel):
    status: RequestStatus
    assigned_to: str | None = None
    intervention_date: datetime | None = None
    commentaire: str | None = None
    auteur: str | None = None


class TariffOut(BaseModel):
    code: str
    libelle: str
    description: str | None = None
    prix: Decimal
    actif: bool
    ordre: int


class TariffUpdate(BaseModel):
    libelle: str | None = Field(default=None, min_length=2, max_length=160)
    description: str | None = None
    prix: Decimal | None = Field(default=None, ge=0)
    actif: bool | None = None
    ordre: int | None = Field(default=None, ge=0)


class QuoteUpsert(BaseModel):
    type_intervention: str = Field(min_length=2, max_length=40)
    statut: QuoteStatus = QuoteStatus.brouillon
    validite_jusqu_au: date | None = None
    observations: str | None = Field(default=None, max_length=3000)

    @field_validator("observations", mode="before")
    @classmethod
    def blank_observations_to_none(cls, value):
        if isinstance(value, str) and not value.strip():
            return None
        return value


class QuoteOut(BaseModel):
    id: uuid.UUID
    demande_id: uuid.UUID
    reference: str
    statut: QuoteStatus
    type_intervention: str
    libelle: str
    prix_unitaire: Decimal
    quantite: int
    montant_total: Decimal
    commune_facturee: str
    date_devis: date
    validite_jusqu_au: date | None = None
    observations: str | None = None
    created_at: datetime
    updated_at: datetime


class InterventionUpsert(BaseModel):
    agent_nom: str | None = None
    debut_intervention: datetime | None = None
    fin_intervention: datetime | None = None
    methode: str | None = None
    resultat: str | None = None
    cout_ht: Decimal | None = Field(default=None, ge=0)
    compte_rendu: str | None = None
    signature_proprietaire_apres: str | None = None


class InterventionOut(InterventionUpsert):
    id: uuid.UUID
    demande_id: uuid.UUID


class MapItemOut(BaseModel):
    id: uuid.UUID
    reference: str
    kind: str
    commune: str
    adresse: str | None
    status: str
    type_observation: str | None = None
    type_demande: str | None = None
    niveau_danger: str | None = None
    niveau_urgence: str | None = None
    description: str | None = None
    longitude: float
    latitude: float
    created_at: datetime


class GeocodingResult(BaseModel):
    label: str
    city: str | None = None
    postcode: str | None = None
    longitude: float
    latitude: float


class StatsOut(BaseModel):
    observations_a_qualifier: int
    demandes_urgentes: int
    demandes_a_qualifier: int
    interventions_programmees: int
    interventions_terminees: int


class PublicConfigOut(BaseModel):
    app_name: str
    portal_title: str
    support_email: str
    support_phone: str
    environment: str
    notifications_enabled: bool


class LoginRequest(BaseModel):
    username: str = Field(min_length=1, max_length=160)
    password: str = Field(min_length=1, max_length=512)


class AuthOut(BaseModel):
    authenticated: bool
    username: str | None = None
    email: str | None = None
    role: UserRole | None = None
    commune_code: str | None = None
    commune_nom: str | None = None
    csrf_token: str | None = None


class CommuneOut(BaseModel):
    insee_com: str
    nom: str
    population: int | None = None


class UserCreate(BaseModel):
    email: EmailStr
    nom: str = Field(min_length=1, max_length=150)
    prenom: str | None = Field(default=None, max_length=150)
    password: str = Field(min_length=12, max_length=512)
    role: UserRole
    commune_code: str | None = Field(default=None, min_length=5, max_length=5)
    actif: bool = True

    @model_validator(mode="after")
    def validate_commune(self):
        if self.role == UserRole.elu and not self.commune_code:
            raise ValueError("Une commune est obligatoire pour un compte élu.")
        return self


class UserUpdate(BaseModel):
    email: EmailStr | None = None
    nom: str | None = Field(default=None, min_length=1, max_length=150)
    prenom: str | None = Field(default=None, max_length=150)
    role: UserRole | None = None
    commune_code: str | None = Field(default=None, min_length=5, max_length=5)
    actif: bool | None = None


class PasswordReset(BaseModel):
    password: str = Field(min_length=12, max_length=512)
    doit_changer_mot_de_passe: bool = False


class UserOut(BaseModel):
    id: uuid.UUID
    email: str
    nom: str
    prenom: str | None = None
    role: UserRole
    commune_code: str | None = None
    commune_nom: str | None = None
    actif: bool
    doit_changer_mot_de_passe: bool
    derniere_connexion: datetime | None = None
    cree_le: datetime
