from __future__ import annotations

import json
from pathlib import Path
from typing import Any

from .cache import get_or_fetch
from .config import CACHE_TTL_DAYS
from .http_client import get_json

TTL_SECONDS = CACHE_TTL_DAYS * 24 * 3600
API_GEO_URL = "https://geo.api.gouv.fr"
SOCIODEMO_DATA_YEAR = 2022


def _cache_file(base_dir: Path, niveau: str, code: str) -> Path:
    return base_dir / "data" / "cache" / f"insee_sociodemo_{niveau}_{code}.json"


def _safe_int(value: Any) -> int | None:
    try:
        if value is None:
            return None
        return int(round(float(value)))
    except Exception:
        return None


def _density(population: Any, surface_ha: Any) -> float | None:
    try:
        pop = float(population or 0)
        surface = float(surface_ha or 0)
        if pop <= 0 or surface <= 0:
            return None
        # API Géo expose la surface en hectares ; 100 ha = 1 km².
        return round(pop / (surface / 100), 1)
    except Exception:
        return None


def _household_size(population: Any, households: Any) -> float | None:
    try:
        pop = float(population or 0)
        men = float(households or 0)
        if pop <= 0 or men <= 0:
            return None
        return round(pop / men, 2)
    except Exception:
        return None


def _normalize_payload(payload: dict[str, Any], niveau: str, code: str) -> dict[str, Any]:
    series = payload.get("series") or []
    normalized_series = []
    for row in series:
        normalized_series.append({
            "year": _safe_int(row.get("year")),
            "population": _safe_int(row.get("population") or row.get("value")),
            "households": _safe_int(row.get("households") or row.get("menages")),
            "density": row.get("density"),
            "type": row.get("type") or "observed",
        })
    normalized_series = [row for row in normalized_series if row.get("year") is not None]
    normalized_series.sort(key=lambda row: row["year"])
    observed = [row for row in normalized_series if row.get("type") == "observed" and row.get("population") is not None]
    last = observed[-1] if observed else (normalized_series[-1] if normalized_series else {})
    first = observed[0] if observed else (normalized_series[0] if normalized_series else {})
    population_current = _safe_int(payload.get("population_current") or last.get("population"))
    households = _safe_int(payload.get("households") or last.get("households"))
    density = payload.get("density") or last.get("density")
    population_change_pct = None
    if first.get("population") and population_current:
        population_change_pct = round(((population_current - first["population"]) / first["population"]) * 100, 1)
    households_change_pct = None
    if first.get("households") and households:
        households_change_pct = round(((households - first["households"]) / first["households"]) * 100, 1)
    return {
        "available": True,
        "official": bool(payload.get("official", True)),
        "source_mode": payload.get("source_mode") or "insee_cache_json",
        "source": payload.get("source") or "INSEE RP",
        "millesime": payload.get("millesime") or str(SOCIODEMO_DATA_YEAR),
        "data_year": int(payload.get("data_year") or SOCIODEMO_DATA_YEAR),
        "territoire": code,
        "niveau": niveau,
        "population_current": population_current,
        "population_label": "Population municipale" if niveau == "commune" else "Population EPCI",
        "population_change_pct": population_change_pct,
        "population_change_since": first.get("year"),
        "population_projection": None,
        "projection_year": None,
        "households": households,
        "households_change_pct": households_change_pct,
        "avg_household_size": _household_size(population_current, households),
        "density": round(float(density), 1) if density not in (None, "") else None,
        "series": normalized_series,
        "methodology": payload.get("methodology") or {
            "observed_source": "INSEE - Recensement de la population",
            "projection_source": "Projections non affichées dans ce chapitre",
            "projection_scale": "Commune" if niveau == "commune" else "EPCI",
            "projection_direct": False,
            "warning": "Données lues depuis le cache INSEE local. Le rafraîchissement peut être automatisé côté serveur. Les projections restent dans le chapitre Anticiper.",
        },
        "sources": payload.get("sources") or [
            {"label": "Population", "source": "INSEE RP", "scale": niveau, "status": "cache"},
            {"label": "Ménages", "source": "INSEE RP", "scale": niveau, "status": "cache"},
        ],
    }


def from_local_cache(code: str, niveau: str, *, base_dir: Path) -> dict[str, Any] | None:
    path = _cache_file(base_dir, niveau, code)
    if not path.exists():
        return None
    try:
        return _normalize_payload(json.loads(path.read_text(encoding="utf-8")), niveau, code)
    except Exception:
        return None


def api_geo_current(code: str, niveau: str) -> dict[str, Any] | None:
    """Récupère population/surface actuelles via API Géo.

    Ce n'est pas suffisant pour les historiques ménages, mais cela donne un
    socle automatique lorsque le cache INSEE RP n'est pas encore préparé.
    """
    if niveau == "commune":
        cache_key = f"api_geo_commune_population_{code}"
        item = get_or_fetch(
            cache_key,
            TTL_SECONDS,
            lambda: get_json(f"{API_GEO_URL}/communes/{code}", params={"fields": "nom,code,population,surface,codeEpci", "format": "json"}),
            source=f"{API_GEO_URL}/communes/{code}",
        )
        payload = item["payload"]
        pop = _safe_int(payload.get("population"))
        surface = payload.get("surface")
        dens = _density(pop, surface)
        return {
            "available": True,
            "official": True,
            "source_mode": "api_geo_current_partial",
            "source": "API Géo / population communale",
            "millesime": str(SOCIODEMO_DATA_YEAR),
            "data_year": SOCIODEMO_DATA_YEAR,
            "territoire": code,
            "niveau": niveau,
            "population_current": pop,
            "population_label": "Population municipale",
            "population_change_pct": None,
            "population_change_since": None,
            "population_projection": None,
            "projection_year": None,
            "households": None,
            "households_change_pct": None,
            "avg_household_size": None,
            "density": dens,
            "series": [{"year": SOCIODEMO_DATA_YEAR, "population": pop, "households": None, "density": dens, "type": "observed"}] if pop else [],
            "methodology": {
                "observed_source": f"API Géo pour population/surface ; affichage provisoire millésime {SOCIODEMO_DATA_YEAR}.",
                "projection_source": "Projections non affichées dans ce chapitre",
                "projection_scale": "Commune",
                "projection_direct": False,
                "warning": "Population et densité actuelles seulement. Pour l'évolution depuis 1970 et les ménages, utiliser le cache INSEE RP ou la vue v_sociodemo_insee.",
            },
            "sources": [{"label": "Population/surface", "source": "API Géo", "scale": "commune", "status": "partiel"}],
        }

    # EPCI : on agrège les communes de l'EPCI via API Géo.
    cache_key = f"api_geo_epci_population_{code}"
    item = get_or_fetch(
        cache_key,
        TTL_SECONDS,
        lambda: get_json(f"{API_GEO_URL}/epcis/{code}/communes", params={"fields": "nom,code,population,surface", "format": "json"}),
        source=f"{API_GEO_URL}/epcis/{code}/communes",
    )
    communes = item["payload"] if isinstance(item["payload"], list) else []
    pop = sum(_safe_int(c.get("population")) or 0 for c in communes)
    surface = sum(float(c.get("surface") or 0) for c in communes)
    dens = _density(pop, surface)
    return {
        "available": True,
        "official": True,
        "source_mode": "api_geo_current_partial",
        "source": "API Géo / agrégation communale EPCI",
        "millesime": str(SOCIODEMO_DATA_YEAR),
        "data_year": SOCIODEMO_DATA_YEAR,
        "territoire": code,
        "niveau": niveau,
        "population_current": pop,
        "population_label": "Population EPCI",
        "population_change_pct": None,
        "population_change_since": None,
        "population_projection": None,
        "projection_year": None,
        "households": None,
        "households_change_pct": None,
        "avg_household_size": None,
        "density": dens,
        "series": [{"year": SOCIODEMO_DATA_YEAR, "population": pop, "households": None, "density": dens, "type": "observed"}] if pop else [],
        "methodology": {
            "observed_source": f"API Géo, agrégation des communes de l'EPCI ; affichage provisoire millésime {SOCIODEMO_DATA_YEAR}.",
            "projection_source": "Projections non affichées dans ce chapitre",
            "projection_scale": "EPCI",
            "projection_direct": False,
            "warning": "Population et densité actuelles seulement. Pour l'évolution depuis 1970 et les ménages, utiliser le cache INSEE RP ou la vue v_sociodemo_insee.",
        },
        "sources": [{"label": "Population/surface", "source": "API Géo", "scale": "EPCI", "status": "partiel"}],
    }


def sociodemo_for_territory(code: str, niveau: str, *, base_dir: Path) -> dict[str, Any] | None:
    cached = from_local_cache(code, niveau, base_dir=base_dir)
    if cached:
        return cached
    return api_geo_current(code, niveau)
