from datetime import date
from dataclasses import dataclass, field
import httpx
import structlog

from app.core.config import settings

logger = structlog.get_logger()


class MotornetUnavailableError(Exception):
    pass


@dataclass
class MotornetVersion:
    motornet_code: str
    version_label: str | None = None
    body_type: str | None = None
    doors: int | None = None
    wheelbase: int | None = None
    list_price: float | None = None
    production_start: date | None = None
    production_end: date | None = None
    commercial_start: date | None = None
    commercial_end: date | None = None
    model_code: str | None = None


@dataclass
class MotornetPlateResult:
    plate: str
    vin: str | None = None
    vehicle_type: str | None = None
    registration_date: date | None = None
    homologation_code: str | None = None
    engine_code: str | None = None
    last_revision_date: date | None = None
    foreign_plate: str | None = None
    foreign_registration_date: date | None = None
    foreign_country: str | None = None
    is_foreign_registered: bool = False
    remaining_queries: int | None = None
    brand_acronym: str | None = None
    brand_name: str | None = None
    model_code: str | None = None
    model_description: str | None = None
    gamma_code: str | None = None
    gamma_description: str | None = None
    series_code: str | None = None
    series_description: str | None = None
    historical_group_code: str | None = None
    historical_group_desc: str | None = None
    cod_desc_model_code: str | None = None
    cod_desc_model_desc: str | None = None
    production_start: date | None = None
    production_end: date | None = None
    commercial_start: date | None = None
    commercial_end: date | None = None
    versions: list[MotornetVersion] = field(default_factory=list)
    raw_response: dict | None = None


def _parse_date(value: str | None) -> date | None:
    if not value:
        return None
    try:
        return date.fromisoformat(value[:10])
    except (ValueError, TypeError):
        return None


def _parse_response(plate: str, data: dict) -> MotornetPlateResult:
    brand = None
    brand_name = None
    if data.get("marche"):
        m = data["marche"][0]
        brand = m.get("acronimo")
        brand_name = m.get("nome")

    model_code = None
    model_description = None
    gamma_code = None
    gamma_description = None
    series_code = None
    series_description = None
    historical_group_code = None
    historical_group_desc = None
    cod_desc_model_code = None
    cod_desc_model_desc = None
    prod_start = None
    prod_end = None
    comm_start = None
    comm_end = None

    if data.get("modelli"):
        mo = data["modelli"][0]
        gm = mo.get("gammaModello") or {}
        model_code = gm.get("codice")
        model_description = gm.get("descrizione")
        gs = mo.get("gruppoStorico") or {}
        historical_group_code = gs.get("codice")
        historical_group_desc = gs.get("descrizione")
        sg = mo.get("serieGamma") or {}
        series_code = sg.get("codice")
        series_description = sg.get("descrizione")
        cdm = mo.get("codDescModello") or {}
        cod_desc_model_code = cdm.get("codice")
        cod_desc_model_desc = cdm.get("descrizione")
        prod_start = _parse_date(mo.get("inizioProduzione"))
        prod_end = _parse_date(mo.get("fineProduzione"))
        comm_start = _parse_date(mo.get("inizioCommercializzazione"))
        comm_end = _parse_date(mo.get("fineCommercializzazione"))

    if data.get("gamme"):
        ga = data["gamme"][0]
        gamma_code = ga.get("codice")
        gamma_description = ga.get("descrizione")

    versions = []
    for v in data.get("versioni") or []:
        versions.append(
            MotornetVersion(
                motornet_code=v["codiceMotornetUnivoco"],
                version_label=v.get("versione"),
                body_type=v.get("tipo"),
                doors=v.get("porte"),
                wheelbase=v.get("passo"),
                list_price=v.get("prezzoVendita"),
                production_start=_parse_date(v.get("inizioProduzione")),
                production_end=_parse_date(v.get("fineProduzione")),
                commercial_start=_parse_date(v.get("da")),
                commercial_end=_parse_date(v.get("a")),
                model_code=v.get("codiceModello"),
            )
        )

    return MotornetPlateResult(
        plate=plate,
        vin=data.get("telaio"),
        vehicle_type=data.get("tipoVeicolo"),
        registration_date=_parse_date(data.get("dataImmatricolazione")),
        homologation_code=data.get("codiceOmologazione"),
        engine_code=data.get("codiceMotore"),
        last_revision_date=_parse_date(data.get("dataUltimaRevisione")),
        foreign_plate=data.get("targaEstero"),
        foreign_registration_date=_parse_date(data.get("dataImmatricolazioneEstero")),
        foreign_country=data.get("statoEstero"),
        is_foreign_registered=data.get("immatricolazioneEstera", "no") == "si",
        remaining_queries=data.get("ricercheTargaRimanenti"),
        brand_acronym=brand,
        brand_name=brand_name,
        model_code=model_code,
        model_description=model_description,
        gamma_code=gamma_code,
        gamma_description=gamma_description,
        series_code=series_code,
        series_description=series_description,
        historical_group_code=historical_group_code,
        historical_group_desc=historical_group_desc,
        cod_desc_model_code=cod_desc_model_code,
        cod_desc_model_desc=cod_desc_model_desc,
        production_start=prod_start,
        production_end=prod_end,
        commercial_start=comm_start,
        commercial_end=comm_end,
        versions=versions,
        raw_response=data,
    )


async def _get_token() -> str:
    url = f"{settings.motornet_oauth_host}/token"
    async with httpx.AsyncClient(timeout=15.0) as client:
        resp = await client.post(
            url,
            data={
                "grant_type": "password",
                "client_id": "webservice",
                "username": settings.motornet_user_id,
                "password": settings.motornet_password,
            },
        )
        resp.raise_for_status()
        return resp.json()["access_token"]


async def valuate_vehicle(
    motornet_code: str,
    anno: int,
    mese: int,
    km: int | None = None,
    targa: str | None = None,
    telaio: str | None = None,
) -> dict:
    try:
        token = await _get_token()
        url = f"{settings.motornet_host}/usato/auto/valutazione"
        payload: dict = {
            "codiceMotornetUnivoco": motornet_code,
            "anno": anno,
            "mese": mese,
        }
        if km is not None:
            payload["km"] = km
        if targa is not None:
            payload["targa"] = targa
        if telaio is not None:
            payload["telaio"] = telaio
        async with httpx.AsyncClient(timeout=30.0) as client:
            resp = await client.post(
                url,
                json=payload,
                headers={"Authorization": f"bearer {token}"},
            )
            resp.raise_for_status()
            data = resp.json()
        logger.info("motornet_valuation_ok", motornet_code=motornet_code, anno=anno, mese=mese)
        return data
    except httpx.HTTPStatusError as exc:
        logger.warning(
            "motornet_valuation_http_error",
            motornet_code=motornet_code,
            status=exc.response.status_code,
        )
        raise MotornetUnavailableError(
            f"MotorNet valutazione ha risposto con status {exc.response.status_code}"
        ) from exc
    except Exception as exc:
        logger.warning("motornet_valuation_unavailable", motornet_code=motornet_code, error=str(exc))
        raise MotornetUnavailableError(str(exc)) from exc


async def lookup_plate(plate: str) -> MotornetPlateResult:
    normalized = plate.upper().replace(" ", "")
    try:
        token = await _get_token()
        url = f"{settings.motornet_host}/usato/generali/targa"
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.get(
                url,
                params={"targa": normalized},
                headers={"Authorization": f"bearer {token}"},
            )
            resp.raise_for_status()
            data = resp.json()
        logger.info("motornet_lookup_ok", plate=normalized)
        return _parse_response(normalized, data)
    except httpx.HTTPStatusError as exc:
        logger.warning(
            "motornet_http_error",
            plate=normalized,
            status=exc.response.status_code,
        )
        raise MotornetUnavailableError(
            f"MotorNet ha risposto con status {exc.response.status_code}"
        ) from exc
    except Exception as exc:
        logger.warning("motornet_unavailable", plate=normalized, error=str(exc))
        raise MotornetUnavailableError(str(exc)) from exc
