"""
Модель воздушных коридоров (v1.1)

Алгоритм:
  - Группировка рейсов по паре аэропортов (orig_icao + dest_icao)
  - Для каждой группы — объединить все точки всех треков
  - Построить буфер 5 км вокруг всех точек (через shapely или fallback haversine bbox)
  - Цвет коридора зависит от московского аэропорта маршрута (приоритет: вылет → прилёт)
  - Возвращает список коридоров с GeoJSON Polygon

Зависимости:
  - shapely (предпочтительно)
  - math (fallback bbox без shapely)

Независим от noise_model.py и density_model.py
"""

import math
import json
from datetime import datetime, timezone

# Попытка импорта shapely
try:
    from shapely.geometry import MultiPoint, mapping
    from shapely.ops import unary_union
    SHAPELY_AVAILABLE = True
except ImportError:
    SHAPELY_AVAILABLE = False


# ─── Цветовая схема по московским аэропортам ────────────────────
AIRPORT_COLORS = {
    "UUEE": "#4A9EFF",  # SVO — Шереметьево, синий
    "UUDD": "#FF8C42",  # DME — Домодедово, оранжевый
    "UUWW": "#A855F7",  # VKO — Внуково, фиолетовый
    "UUBW": "#FFD700",  # ZIA — Жуковский, жёлтый
}
DEFAULT_COLOR = "#AAAAAA"  # серый — если ни один московский аэропорт не найден


def get_corridor_color(orig_icao: str, dest_icao: str) -> str:
    """
    Цвет коридора по московскому аэропорту маршрута.
    Приоритет: аэропорт вылета → аэропорт прилёта → серый.
    """
    if orig_icao in AIRPORT_COLORS:
        return AIRPORT_COLORS[orig_icao]
    if dest_icao in AIRPORT_COLORS:
        return AIRPORT_COLORS[dest_icao]
    return DEFAULT_COLOR


# ─── Буфер через shapely ─────────────────────────────────────────
BUFFER_KM = 5.0   # радиус буфера в км

# Приблизительные метры на градус (для shapely буфера в градусах)
M_PER_DEG = 111320.0


def _buffer_shapely(lat_lon_points: list, buffer_km: float) -> dict:
    """
    Строит буфер вокруг MultiPoint через shapely.
    Возвращает GeoJSON Polygon/MultiPolygon.
    """
    # Вычисляем центр для конвертации коэффициента долготы
    lats = [p[0] for p in lat_lon_points]
    lons = [p[1] for p in lat_lon_points]
    lat_center = sum(lats) / len(lats)

    # Буфер в градусах (приблизительно)
    buf_deg_lat = buffer_km * 1000 / M_PER_DEG
    buf_deg_lon = buffer_km * 1000 / (M_PER_DEG * math.cos(math.radians(lat_center)))

    # Используем среднее как приближение
    buf_deg = (buf_deg_lat + buf_deg_lon) / 2

    # Создаём MultiPoint из (lon, lat) для shapely (стандарт GeoJSON: lon первый)
    mp = MultiPoint([(p[1], p[0]) for p in lat_lon_points])

    # Буфер — приближение через среднее buf_deg
    buffered = mp.buffer(buf_deg, resolution=16)

    # Упрощаем геометрию для меньшего размера
    buffered = buffered.simplify(buf_deg * 0.1)

    return mapping(buffered)


def _buffer_bbox_fallback(lat_lon_points: list, buffer_km: float) -> dict:
    """
    Упрощённый буфер через bounding box + отступ по haversine.
    Используется если shapely недоступна.
    Возвращает GeoJSON Polygon (прямоугольник).
    """
    lats = [p[0] for p in lat_lon_points]
    lons = [p[1] for p in lat_lon_points]

    lat_min = min(lats)
    lat_max = max(lats)
    lon_min = min(lons)
    lon_max = max(lons)

    lat_center = (lat_min + lat_max) / 2

    # Отступ в градусах
    dlat = buffer_km / 111.32
    dlon = buffer_km / (111.32 * math.cos(math.radians(lat_center)))

    lat_min -= dlat
    lat_max += dlat
    lon_min -= dlon
    lon_max += dlon

    # Прямоугольный полигон
    coords = [
        [lon_min, lat_min],
        [lon_min, lat_max],
        [lon_max, lat_max],
        [lon_max, lat_min],
        [lon_min, lat_min],  # закрываем контур
    ]

    return {
        "type": "Polygon",
        "coordinates": [coords],
    }


def _build_buffer(lat_lon_points: list, buffer_km: float) -> dict:
    """Выбирает метод буферизации: shapely или fallback bbox"""
    if not lat_lon_points:
        return None

    if SHAPELY_AVAILABLE:
        try:
            return _buffer_shapely(lat_lon_points, buffer_km)
        except Exception as e:
            # Fallback при ошибке shapely
            pass

    return _buffer_bbox_fallback(lat_lon_points, buffer_km)


# ─── Основная функция ─────────────────────────────────────────────

def compute_corridors(flights: list) -> list:
    """
    Вычисляет воздушные коридоры по списку рейсов.

    Args:
        flights: список рейсов в формате normalize_flight_for_map()
                 или сырые рейсы с полями orig_icao, dest_icao, points[{lat, lon}]

    Returns:
        Список словарей:
        {
            "route": "UUEE-URKK",
            "flight_count": 5,
            "color": "#FFCC00",
            "opacity": 0.25,
            "geometry": { ...GeoJSON Polygon... }
        }
    """
    # Группируем рейсы по маршруту
    groups = {}  # "UUEE-URKK" → список рейсов

    for flight in flights:
        orig = flight.get("orig_icao") or ""
        dest = flight.get("dest_icao") or ""

        # Пропускаем рейсы без обоих ICAO-кодов
        if not orig or not dest:
            continue

        route_key = f"{orig}-{dest}"
        if route_key not in groups:
            groups[route_key] = []
        groups[route_key].append(flight)

    corridors = []

    for route_key, route_flights in groups.items():
        # Собираем все точки всех треков группы
        all_points = []
        for fl in route_flights:
            for pt in fl.get("points", []):
                lat = pt.get("lat")
                lon = pt.get("lon")
                if lat is not None and lon is not None:
                    all_points.append((float(lat), float(lon)))

        # Пропускаем если нет точек
        if len(all_points) < 2:
            continue

        # Строим буфер
        geometry = _build_buffer(all_points, BUFFER_KM)
        if geometry is None:
            continue

        flight_count = len(route_flights)

        if flight_count < 3:
            continue  # пропустить одиночные рейсы

        orig_icao, dest_icao = route_key.split("-", 1)
        color = get_corridor_color(orig_icao, dest_icao)

        corridors.append({
            "route": route_key,
            "flight_count": flight_count,
            "color": color,
            "opacity": 0.25,
            "geometry": geometry,
        })

    # Сортируем по убыванию числа рейсов
    corridors.sort(key=lambda x: -x["flight_count"])
    return corridors


if __name__ == "__main__":
    # Быстрая проверка на реальных данных
    import glob
    from pathlib import Path

    data_dir = Path("data")
    all_flights = []

    for fp in sorted(data_dir.glob("flights_*.json")):
        with open(fp, encoding="utf-8") as f:
            d = json.load(f)
        all_flights.extend(d.get("flights", []))

    print(f"Загружено рейсов: {len(all_flights)}")
    corridors = compute_corridors(all_flights)
    print(f"Коридоров: {len(corridors)}")
    print(f"Shapely: {'доступен' if SHAPELY_AVAILABLE else 'НЕДОСТУПЕН (используется bbox fallback)'}")
    print()
    print("Топ-10 коридоров:")
    for c in corridors[:10]:
        geom_type = c["geometry"].get("type", "?")
        print(f"  {c['route']:20s}  рейсов: {c['flight_count']:3d}  цвет: {c['color']}  геометрия: {geom_type}")
    print()
    print("Легенда цветов:")
    for icao, color in AIRPORT_COLORS.items():
        print(f"  {icao}: {color}")
