"""
Модель плотности пролётов воздушных судов (v1.0)

Алгоритм:
  - Сетка ячеек 500×500 м над всей областью загруженных треков
  - Для каждого рейса и каждой точки трека — найти ячейки в радиусе влияния
  - Каждый рейс учитывается в ячейке не более 1 раза (дедупликация)
  - Результат: {count, min_altitude_m} на ячейку

Радиусы влияния (из БТ):
  H < 1800 м  → R = 2 км
  H < 5000 м  → R = 4 км
  H < 7000 м  → R = 7 км
  H ≥ 7000 м  → не считать
"""

import json
import math
import time
from pathlib import Path
from datetime import datetime, timezone

# ── Параметры ────────────────────────────────────────────────────

GRID_SIZE_M = 500  # размер ячейки в метрах

# Радиусы влияния по высоте (метры → км)
ALTITUDE_RADIUS = [
    (1800, 2.0),   # H < 1800 м → R = 2 км
    (5000, 4.0),   # H < 5000 м → R = 4 км
    (7000, 7.0),   # H < 7000 м → R = 7 км
]
MAX_ALTITUDE_M = 7000  # выше — не считаем

# Константы для геодезических расчётов
METERS_PER_DEG_LAT = 111320.0  # метров на градус широты


def meters_per_deg_lon(lat_deg: float) -> float:
    """Метров на градус долготы на данной широте"""
    return 111320.0 * math.cos(math.radians(lat_deg))


def get_radius_km(altitude_m: float) -> float:
    """Радиус влияния (км) для данной высоты"""
    for max_alt, radius in ALTITUDE_RADIUS:
        if altitude_m < max_alt:
            return radius
    return 0.0  # ≥ 7000 м — не считаем


def lat_to_row(lat: float, lat_min: float) -> int:
    """Широта → номер строки сетки"""
    return int((lat - lat_min) * METERS_PER_DEG_LAT / GRID_SIZE_M)


def lon_to_col(lon: float, lon_min: float, lat_center: float) -> int:
    """Долгота → номер столбца сетки"""
    return int((lon - lon_min) * meters_per_deg_lon(lat_center) / GRID_SIZE_M)


def row_to_lat(row: int, lat_min: float) -> float:
    """Номер строки → широта центра ячейки"""
    return lat_min + (row + 0.5) * GRID_SIZE_M / METERS_PER_DEG_LAT


def col_to_lon(col: int, lon_min: float, lat_center: float) -> float:
    """Номер столбца → долгота центра ячейки"""
    return lon_min + (col + 0.5) * GRID_SIZE_M / meters_per_deg_lon(lat_center)


def load_all_flights(data_dir: Path, date_from: str = None, date_to: str = None) -> list:
    """Загружает рейсы из flights_*.json с опциональным фильтром по датам"""
    flights = []
    for fp in sorted(data_dir.glob("flights_*.json")):
        try:
            with open(fp, encoding="utf-8") as f:
                d = json.load(f)
            file_date = d.get("date", "")
            if date_from and file_date and file_date < date_from:
                continue
            if date_to and file_date and file_date > date_to:
                continue
            file_date = d.get("date", "")
            batch = d.get("flights", []) if isinstance(d, dict) else d
            for fl in batch:
                if not fl.get("date") and file_date:
                    fl["date"] = file_date
            flights.extend(batch)
        except Exception as e:
            print(f"  ⚠️ Ошибка чтения {fp.name}: {e}")
    return flights


def compute_density(data_dir: Path, date_from: str = None, date_to: str = None) -> dict:
    """
    Основная функция расчёта сетки плотности.
    Возвращает словарь для /api/density.
    """
    t0 = time.time()
    print("🔢 Расчёт плотности пролётов...")

    flights = load_all_flights(data_dir, date_from=date_from, date_to=date_to)
    if not flights:
        return {"error": "Нет данных", "cells": []}

    print(f"   Рейсов загружено: {len(flights)}")

    # ── Собираем все точки для определения bbox ──────────────────
    all_lats = []
    all_lons = []
    for fl in flights:
        for pt in fl.get("points", []):
            lat, lon = pt.get("lat"), pt.get("lon")
            if lat and lon:
                all_lats.append(lat)
                all_lons.append(lon)

    if not all_lats:
        return {"error": "Нет точек треков", "cells": []}

    # Расширяем bbox на максимальный радиус (7 км)
    margin_lat = 7.0 / 111.32
    margin_lon = 7.0 / (111.32 * math.cos(math.radians(sum(all_lats) / len(all_lats))))

    lat_min = min(all_lats) - margin_lat
    lat_max = max(all_lats) + margin_lat
    lon_min = min(all_lons) - margin_lon
    lon_max = max(all_lons) + margin_lon
    lat_center = (lat_min + lat_max) / 2

    rows = lat_to_row(lat_max, lat_min) + 1
    cols = lon_to_col(lon_max, lon_min, lat_center) + 1

    print(f"   bbox: {lat_min:.3f}–{lat_max:.3f}N, {lon_min:.3f}–{lon_max:.3f}E")
    print(f"   Сетка: {rows} × {cols} = {rows * cols:,} ячеек")

    # ── Сетка: {(row, col): {count, min_alt, flight_ids}} ─────────
    grid = {}  # (row, col) → {'count': int, 'min_alt': float, 'seen': set}

    total_points = 0
    skipped_high = 0

    for fl in flights:
        flight_id = fl.get("id", "") or fl.get("callsign", "")
        points = fl.get("points", [])

        # Ячейки, которые этот рейс уже затронул — для дедупликации
        touched_cells = set()

        for pt in points:
            lat = pt.get("lat")
            lon = pt.get("lon")
            alt_m = pt.get("altitude_m", 0) or 0

            if not lat or not lon:
                continue

            total_points += 1

            # Фильтр по высоте
            if alt_m >= MAX_ALTITUDE_M:
                skipped_high += 1
                continue

            radius_km = get_radius_km(alt_m)
            if radius_km == 0:
                continue

            # Радиус в ячейках
            radius_rows = int(math.ceil(radius_km * 1000 / GRID_SIZE_M))
            radius_cols = int(math.ceil(radius_km * 1000 / GRID_SIZE_M))

            # Центральная ячейка
            center_row = lat_to_row(lat, lat_min)
            center_col = lon_to_col(lon, lon_min, lat_center)

            # Перебираем ячейки в квадрате (потом фильтруем по радиусу)
            for dr in range(-radius_rows, radius_rows + 1):
                for dc in range(-radius_cols, radius_cols + 1):
                    r = center_row + dr
                    c = center_col + dc

                    if r < 0 or r >= rows or c < 0 or c >= cols:
                        continue

                    # Точное расстояние до центра ячейки
                    cell_lat = row_to_lat(r, lat_min)
                    cell_lon = col_to_lon(c, lon_min, lat_center)

                    dlat_m = (cell_lat - lat) * METERS_PER_DEG_LAT
                    dlon_m = (cell_lon - lon) * meters_per_deg_lon(lat_center)
                    dist_m = math.sqrt(dlat_m**2 + dlon_m**2)

                    if dist_m > radius_km * 1000:
                        continue

                    cell_key = (r, c)

                    # Дедупликация: рейс учитывается в ячейке 1 раз
                    dedup_key = (flight_id, r, c)
                    if dedup_key in touched_cells:
                        continue
                    touched_cells.add(dedup_key)

                    if cell_key not in grid:
                        grid[cell_key] = {'count': 0, 'min_alt': float('inf')}

                    grid[cell_key]['count'] += 1
                    grid[cell_key]['min_alt'] = min(grid[cell_key]['min_alt'], alt_m)

    # ── Формируем результат ──────────────────────────────────────
    cells = []
    for (r, c), data in grid.items():
        cells.append({
            "lat": round(row_to_lat(r, lat_min), 4),
            "lon": round(col_to_lon(c, lon_min, lat_center), 4),
            "count": data['count'],
            "min_altitude_m": int(data['min_alt']) if data['min_alt'] != float('inf') else 0,
        })

    # Фильтруем шум — оставляем только ячейки где было ≥5 рейсов
    cells = [c for c in cells if c['count'] >= 5]

    # Считаем уникальные дни в данных → для нормировки на час
    unique_dates = set()
    for fl in flights:
        d = fl.get("date", "")
        if d:
            unique_dates.add(d)
    num_days  = max(len(unique_dates), 1)
    num_hours = num_days * 24

    # Переводим count → рейсов/час (округляем до 2 знаков)
    for c in cells:
        c["flights_per_hour"] = round(c["count"] / num_hours, 2)

    # Сортируем по убыванию
    cells.sort(key=lambda x: -x["flights_per_hour"])

    # Фильтруем — минимум 5 пролётов (чтобы не было мусора от 1 рейса)
    cells = [c for c in cells if c["count"] >= 5]

    elapsed   = round(time.time() - t0, 1)
    max_fph   = cells[0]["flights_per_hour"] if cells else 0
    max_count = cells[0]["count"] if cells else 0

    print(f"   Ячеек с данными: {len(cells):,}")
    print(f"   Уникальных дней: {num_days} ({num_hours}ч)")
    print(f"   Макс. рейсов/час: {max_fph}")
    print(f"   Точек обработано: {total_points:,}, пропущено (высота): {skipped_high:,}")
    print(f"   Время расчёта: {elapsed}с")

    return {
        "grid_size_m": GRID_SIZE_M,
        "bbox": [
            round(lon_min, 6), round(lat_min, 6),
            round(lon_max, 6), round(lat_max, 6),
        ],
        "cells": cells,
        "total_cells": len(cells),
        "flights_used": len(flights),
        "num_days": num_days,
        "num_hours": num_hours,
        "max_count": max_count,
        "max_flights_per_hour": max_fph,
        "calc_time_sec": elapsed,
        "generated_at": datetime.now(timezone.utc).isoformat(),
    }


if __name__ == "__main__":
    # Тест
    data_dir = Path("data")
    result = compute_density(data_dir)
    print(f"\nТоп-10 ячеек:")
    for cell in result["cells"][:10]:
        print(f"  {cell['lat']:.4f}, {cell['lon']:.4f} → {cell['count']} рейсов, мин. высота {cell['min_altitude_m']} м")
