"""Pipeline: genera isócronas por zona geográfica usando p75 de tiempos de los puntos en cada zona."""

from __future__ import annotations

import asyncio
import math
import sys
import os

_FUNCTIONS_DIR = os.path.join(os.path.dirname(__file__), "..")
if _FUNCTIONS_DIR not in sys.path:
    sys.path.insert(0, _FUNCTIONS_DIR)

from geo.load_geojson_polygons import load_geojson_polygons
from geo.polygon_bbox import polygon_bbox
from geo.point_in_polygons_bbox import point_in_polygons_bbox
from datascience.summary_stats import summary_stats
from geo.valhalla_isochrones_async import valhalla_isochrones_async


async def generate_isochrones_by_zone_pipeline(
    zones: list[dict],
    points: list[dict],
    centers: list[dict],
    base_url: str = "http://localhost:8002",
    concurrency: int = 6,
) -> dict:
    """Genera isócronas por zona usando el p75 de tiempos de viaje de los puntos en cada zona.

    Para cada zona:
    1. Carga los polígonos GeoJSON de la zona (y de exclusión si se indica).
    2. Filtra los points que caen dentro de la zona (no en exclusión).
    3. Calcula el p75(seconds/60) → minutos de isócrona representativa.
    4. Filtra los centers que caen en la zona.
    5. Genera una isócrona Valhalla por cada center con esos minutos.

    Args:
        zones: Lista de dicts con:
               - "label" (str): nombre de la zona.
               - "geojson_path" (str): ruta al GeoJSON de la zona.
               - "exclude_geojson_path" (str | None): ruta GeoJSON de exclusión (opcional).
        points: Lista de dicts con "lat" (float), "lon" (float) y "seconds" (float | None).
                Los puntos sin seconds o con seconds=None se omiten del cálculo de p75.
        centers: Lista de dicts con "lat" (float), "lon" (float) e "id" (str).
        base_url: URL base del servidor Valhalla.
        concurrency: Número máximo de requests async simultáneos para isócronas.

    Returns:
        Dict con clave "zones": lista de dicts con:
          - "label" (str): nombre de la zona.
          - "minutes" (float | None): p75 en minutos usado para las isócronas. None si sin datos.
          - "n_points" (int): puntos en la zona con seconds válidos.
          - "n_centers" (int): centros en la zona.
          - "isochrones" (list[dict | None]): GeoJSON por cada center de la zona.
    """
    zone_results = []

    for zone in zones:
        label = zone["label"]
        geojson_path = zone["geojson_path"]
        exclude_path = zone.get("exclude_geojson_path")

        # 1. Cargar polígonos de la zona
        polygons = load_geojson_polygons(geojson_path)
        bboxes = [polygon_bbox(p) for p in polygons]

        # Cargar polígonos de exclusión si se indica
        exclude_polygons: list = []
        exclude_bboxes: list = []
        if exclude_path:
            exclude_polygons = load_geojson_polygons(exclude_path)
            exclude_bboxes = [polygon_bbox(p) for p in exclude_polygons]

        # 2. Filtrar points en zona (y no en exclusión)
        zone_seconds: list[float] = []
        for pt in points:
            lon = float(pt["lon"])
            lat = float(pt["lat"])
            secs = pt.get("seconds")
            if secs is None or (isinstance(secs, float) and math.isnan(secs)):
                continue
            if not point_in_polygons_bbox(lon, lat, polygons, bboxes):
                continue
            if exclude_polygons and point_in_polygons_bbox(lon, lat, exclude_polygons, exclude_bboxes):
                continue
            zone_seconds.append(float(secs))

        # 3. Calcular p75 en minutos
        minutes: float | None = None
        if zone_seconds:
            stats = summary_stats(zone_seconds)
            p75_secs = stats.get("p75", math.nan)
            if not math.isnan(p75_secs):
                minutes = p75_secs / 60.0

        # 4. Filtrar centers en zona
        zone_centers = [
            c for c in centers
            if point_in_polygons_bbox(float(c["lon"]), float(c["lat"]), polygons, bboxes)
        ]

        # 5. Generar isócronas para cada center de la zona
        isochrones: list = []
        if zone_centers and minutes is not None and minutes > 0:
            iso_requests = [
                {
                    "lat": float(c["lat"]),
                    "lon": float(c["lon"]),
                    "minutes": max(1, round(minutes)),
                    "id": c.get("id", str(idx)),
                }
                for idx, c in enumerate(zone_centers)
            ]
            isochrones = await valhalla_isochrones_async(
                requests=iso_requests,
                base_url=base_url,
                concurrency=concurrency,
            )

        zone_results.append(
            {
                "label": label,
                "minutes": minutes,
                "n_points": len(zone_seconds),
                "n_centers": len(zone_centers),
                "isochrones": list(isochrones),
            }
        )

    return {"zones": zone_results}
