from __future__ import annotations

from datetime import datetime
from typing import Any

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


def _validate_iso_datetime(value: str) -> str:
    candidate = value.strip()
    if not candidate:
        raise ValueError("Datetime value cannot be empty.")
    try:
        datetime.fromisoformat(candidate.replace("Z", "+00:00"))
        return candidate
    except ValueError as exc:
        raise ValueError("Datetime fields must follow ISO 8601 format.") from exc


class EntregaBase(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="forbid")

    entregador_id: str | None = Field(default=None, alias="entregadorId", max_length=255)
    empresa_id: str | None = Field(default=None, alias="empresaId", max_length=255)
    endereco_entrega: str | None = Field(default=None, alias="enderecoEntrega", max_length=255)
    cidade_entrega: str | None = Field(default=None, alias="cidadeEntrega", max_length=255)
    estado_entrega: str | None = Field(default=None, alias="estadoEntrega", max_length=255)
    cep_entrega: str | None = Field(default=None, alias="cepEntrega", max_length=20)
    pais_entrega: str | None = Field(default=None, alias="paisEntrega", max_length=100)
    bairro_entrega: str | None = Field(default=None, alias="bairroEntrega", max_length=255)
    numero_entrega: str | None = Field(default=None, alias="numeroEntrega", max_length=50)
    complemento_entrega: str | None = Field(default=None, alias="complementoEntrega", max_length=255)
    nome_destinatario: str | None = Field(default=None, alias="nomeDestinatario", max_length=255)
    telefone_destinatario: str | None = Field(default=None, alias="telefoneDestinatario", max_length=50)
    email_destinatario: EmailStr | None = Field(default=None, alias="emailDestinatario")
    status: str | None = Field(default=None, alias="status", max_length=50)
    codigo_rastreamento: str | None = Field(default=None, alias="codigoRastreamento", max_length=255)
    data_entrega_estimativa: str | None = Field(default=None, alias="dataEntregaEstimativa")
    data_entrega_real: str | None = Field(default=None, alias="dataEntregaReal")
    data_coleta: str | None = Field(default=None, alias="dataColeta")
    data_atribuicao: str | None = Field(default=None, alias="dataAtribuicao")
    data_criacao: str | None = Field(default=None, alias="dataCriacao")
    data_atualizacao: str | None = Field(default=None, alias="dataAtualizacao")
    taxa_entrega: float | None = Field(default=None, alias="taxaEntrega", ge=0)
    distancia: float | None = Field(default=None, alias="distancia", ge=0)
    observacoes_entrega: str | None = Field(default=None, alias="observacoesEntrega")
    url_comprovante_entrega: str | None = Field(default=None, alias="urlComprovanteEntrega")
    assinatura_entrega: str | None = Field(default=None, alias="assinaturaEntrega")
    motivo_falha: str | None = Field(default=None, alias="motivoFalha")
    motivo_cancelamento: str | None = Field(default=None, alias="motivoCancelamento")

    @field_validator(
        "data_entrega_estimativa",
        "data_entrega_real",
        "data_coleta",
        "data_atribuicao",
        "data_criacao",
        "data_atualizacao",
        mode="before",
    )
    @classmethod
    def _validate_datetimes(cls, value: Any) -> Any:
        if value is None:
            return value
        if not isinstance(value, str):
            raise TypeError("Datetime fields must be provided as ISO 8601 strings.")
        return _validate_iso_datetime(value)


class EntregaCreate(EntregaBase):
    empresa_id: str = Field(..., alias="empresaId", min_length=1, max_length=255)
    endereco_entrega: str = Field(..., alias="enderecoEntrega", min_length=1, max_length=255)
    cidade_entrega: str = Field(..., alias="cidadeEntrega", min_length=1, max_length=255)
    estado_entrega: str = Field(..., alias="estadoEntrega", min_length=1, max_length=255)
    cep_entrega: str = Field(..., alias="cepEntrega", min_length=1, max_length=20)
    pais_entrega: str = Field(..., alias="paisEntrega", min_length=1, max_length=100)
    nome_destinatario: str = Field(..., alias="nomeDestinatario", min_length=1, max_length=255)
    status: str = Field(..., alias="status", min_length=1, max_length=50)
    codigo_rastreamento: str = Field(..., alias="codigoRastreamento", min_length=1, max_length=255)


class EntregaCreateRequest(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="forbid")

    document_id: str | None = Field(default=None, alias="documentId", min_length=1, max_length=255)
    data: EntregaCreate = Field(alias="data")


class EntregaUpdate(EntregaBase):
    pass


class DestinoCotacao(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="forbid")

    endereco_entrega: str | None = Field(default=None, alias="enderecoEntrega", max_length=255)
    numero_entrega: str | None = Field(default=None, alias="numeroEntrega", max_length=50)
    bairro_entrega: str | None = Field(default=None, alias="bairroEntrega", max_length=255)
    cidade_entrega: str | None = Field(default=None, alias="cidadeEntrega", max_length=255)
    estado_entrega: str | None = Field(default=None, alias="estadoEntrega", max_length=255)
    cep_entrega: str | None = Field(default=None, alias="cepEntrega", max_length=20)
    pais_entrega: str | None = Field(default="Brasil", alias="paisEntrega", max_length=100)
    latitude: float | None = Field(default=None, alias="latitude")
    longitude: float | None = Field(default=None, alias="longitude")

    @model_validator(mode="after")
    def _validate_destino(self) -> "DestinoCotacao":
        has_coords = self.latitude is not None and self.longitude is not None
        has_min_address = (
            (self.endereco_entrega and self.endereco_entrega.strip())
            and (self.cidade_entrega and self.cidade_entrega.strip())
            and (self.estado_entrega and self.estado_entrega.strip())
        )
        if not has_coords and not has_min_address:
            raise ValueError(
                "Informe latitude/longitude ou um endereco com cidade e estado para calcular o frete."
            )
        return self


class CotacaoFreteRequest(BaseModel):
    model_config = ConfigDict(populate_by_name=True, extra="forbid")

    empresa_id: str = Field(..., alias="empresaId", min_length=1, max_length=255)
    destino: DestinoCotacao


class CotacaoFreteResponse(BaseModel):
    valor: float = Field(alias="valor")
    distancia_km: float = Field(alias="distanciaKm")
    tipo_frete: str = Field(alias="tipoFrete")
    moeda: str = Field(default="BRL", alias="moeda")

    model_config = ConfigDict(populate_by_name=True)


class EntregaOut(EntregaBase):
    id: str = Field(alias="id")
    empresa_id: str = Field(alias="empresaId")
    endereco_entrega: str = Field(alias="enderecoEntrega")
    cidade_entrega: str = Field(alias="cidadeEntrega")
    estado_entrega: str = Field(alias="estadoEntrega")
    cep_entrega: str = Field(alias="cepEntrega")
    pais_entrega: str = Field(alias="paisEntrega")
    nome_destinatario: str = Field(alias="nomeDestinatario")
    status: str = Field(alias="status")
    codigo_rastreamento: str = Field(alias="codigoRastreamento")
    created_at: str = Field(alias="createdAt")
    updated_at: str = Field(alias="updatedAt")

    model_config = ConfigDict(from_attributes=True, populate_by_name=True)
