"""
Yoro FastAPI application.

Lightweight HTTP API for encoding/decoding Altius codes.
Can be embedded in an existing FastAPI app or run standalone.
"""

from __future__ import annotations

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field

from yoro import __version__
from yoro.codec import (
    decode,
    domain_for_country,
    encode,
    neighbors,
    precision_levels,
    resolution,
)
from yoro.constants import DOMAINS


# ---------- Pydantic schemas ----------

class BoundsSchema(BaseModel):
    lat_min: float
    lat_max: float
    lon_min: float
    lon_max: float


class EncodeResponse(BaseModel):
    code: str
    lat: float
    lon: float
    precision: int
    resolution_m: float
    bounds: BoundsSchema


class DecodeResponse(BaseModel):
    code: str
    lat: float
    lon: float
    precision: int
    domain: str
    resolution_m: float
    bounds: BoundsSchema
    neighbors: list[str]


class DomainSchema(BaseModel):
    name: str
    bounds: BoundsSchema


class PrecisionLevelSchema(BaseModel):
    precision: int
    code_length: int
    grid_size: int
    total_cells: int
    resolution_m: float


class PrecisionsResponse(BaseModel):
    domain: str
    levels: list[PrecisionLevelSchema]


class DomainsResponse(BaseModel):
    domains: dict[str, DomainSchema]


# ---------- Router ----------

def create_app(prefix: str = "") -> FastAPI:
    """Create the Yoro FastAPI application.

    Args:
        prefix: Optional URL prefix for all routes (e.g. ``"/api/v1/yoro"``).
    """
    app = FastAPI(
        title="Yoro API",
        description="Geographic addressing via Hilbert curves — GPS ↔ compact codes",
        version=__version__,
    )

    @app.get(f"{prefix}/encode", response_model=EncodeResponse)
    def encode_endpoint(
        lat: float = Query(..., description="Latitude (WGS 84)"),
        lon: float = Query(..., description="Longitude (WGS 84)"),
        domain: str = Query("CI", description="Domain ISO code"),
        precision: int = Query(12, ge=1, le=20, description="Hilbert order"),
    ) -> EncodeResponse:
        domain = domain_for_country(domain)
        try:
            code_str = encode(lat, lon, precision=precision, domain=domain)
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))

        decoded = decode(code_str)
        res = resolution(decoded["precision"], domain=domain)

        return EncodeResponse(
            code=code_str,
            lat=decoded["lat"],
            lon=decoded["lon"],
            precision=decoded["precision"],
            resolution_m=round(res, 2),
            bounds=BoundsSchema(**decoded["bounds"]),
        )

    @app.get(f"{prefix}/decode", response_model=DecodeResponse)
    def decode_endpoint(
        code: str = Query(..., description="Yoro string (e.g. CI-4H7A3B)"),
    ) -> DecodeResponse:
        if "-" not in code:
            raise HTTPException(
                status_code=400,
                detail="code must contain a dash (e.g. 'CI-4H7A3B')",
            )

        try:
            decoded = decode(code)
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))

        res = resolution(decoded["precision"], domain=decoded["domain"])
        nbrs = neighbors(code)

        return DecodeResponse(
            code=code.upper(),
            lat=decoded["lat"],
            lon=decoded["lon"],
            precision=decoded["precision"],
            domain=decoded["domain"],
            resolution_m=round(res, 2),
            bounds=BoundsSchema(**decoded["bounds"]),
            neighbors=nbrs,
        )

    @app.get(f"{prefix}/precisions", response_model=PrecisionsResponse)
    def precisions_endpoint(
        domain: str = Query("CI", description="Domain ISO code"),
        max_code_length: int = Query(10, ge=1, le=15, description="Max code characters"),
    ) -> PrecisionsResponse:
        domain = domain_for_country(domain)
        try:
            levels = precision_levels(domain=domain, max_code_length=max_code_length)
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))
        return PrecisionsResponse(
            domain=domain,
            levels=[PrecisionLevelSchema(**lv) for lv in levels],
        )

    @app.get(f"{prefix}/domains", response_model=DomainsResponse)
    def domains_endpoint() -> DomainsResponse:
        result = {}
        for key, dom in DOMAINS.items():
            result[key] = DomainSchema(
                name=dom["name"],
                bounds=BoundsSchema(
                    lat_min=dom["lat_min"],
                    lat_max=dom["lat_max"],
                    lon_min=dom["lon_min"],
                    lon_max=dom["lon_max"],
                ),
            )
        return DomainsResponse(domains=result)

    @app.get(f"{prefix}/health")
    def health() -> dict:
        return {"status": "ok", "version": __version__}

    return app
