"""
FR24 tracks worker.
1. GET /api/flight-summary/light for each day → list of fr24_ids
2. GET /api/flight-tracks?flight_id={fr24_id} → track points
3. Upsert into fr24_ext.flight_tracks_fr24 + fr24_ext.track_points_fr24
"""
import logging
import time
from datetime import date, datetime, timezone
from typing import Iterator, List, Dict, Optional

import requests
import psycopg2

from config import config

log = logging.getLogger("fr24_worker")

HEADERS = {
    "Authorization": f"Bearer {config.FR24_API_KEY}",
    "Accept": "application/json",
    "Accept-Version": "v1",
}

_last_request_at: float = 0.0


def _throttle():
    """Enforce rate limit: max 10 req/min → sleep if needed."""
    global _last_request_at
    elapsed = time.monotonic() - _last_request_at
    if elapsed < config.RATE_LIMIT_SEC:
        time.sleep(config.RATE_LIMIT_SEC - elapsed)
    _last_request_at = time.monotonic()


def _get(path: str, params: dict = None) -> dict:
    _throttle()
    url = f"{config.FR24_API_BASE}{path}"
    resp = requests.get(url, headers=HEADERS, params=params, timeout=30)
    if resp.status_code == 429:
        retry_after = int(resp.headers.get("Retry-After", 60))
        log.warning("Rate limited, sleeping %ds", retry_after)
        time.sleep(retry_after)
        return _get(path, params)
    resp.raise_for_status()
    return resp.json()


def iter_flight_summaries(target_date: date) -> Iterator[Dict]:
    """Paginate through flight-summary/light for all 4 airports."""
    dt_from = f"{target_date}T00:00:00"
    dt_to   = f"{target_date}T23:59:59"
    offset  = 0

    while True:
        data = _get("/api/flight-summary/light", params={
            "flight_datetime_from": dt_from,
            "flight_datetime_to":   dt_to,
            "airports":             config.AIRPORTS,
            "limit":                config.PAGE_SIZE,
            "offset":               offset,
        })

        items = data.get("data", data) if isinstance(data, dict) else data
        if not items:
            break

        for item in items:
            yield item

        # pagination: if fewer items than page size, we're done
        if len(items) < config.PAGE_SIZE:
            break
        offset += len(items)


def fetch_track(fr24_id: str) -> Optional[List[Dict]]:
    """Fetch track points for a single flight."""
    try:
        data = _get("/api/flight-tracks", params={"flight_id": fr24_id})
        # response is a list of {fr24_id, tracks: [...]}
        if isinstance(data, list) and data:
            return data[0].get("tracks", [])
        if isinstance(data, dict):
            return data.get("tracks", [])
        return []
    except requests.HTTPError as e:
        log.warning("Failed to fetch track for %s: %s", fr24_id, e)
        return None


def upsert_flight(conn, summary: Dict, target_date: date) -> Optional[int]:
    """Insert/update flight header, return DB id."""
    with conn.cursor() as cur:
        cur.execute(
            """
            INSERT INTO fr24_ext.flight_tracks_fr24
              (fr24_id, flight_number, callsign, aircraft_type, registration,
               origin_icao, destination_icao, actual_takeoff, actual_landed, flight_date)
            VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
            ON CONFLICT (fr24_id) DO UPDATE SET
              flight_number    = EXCLUDED.flight_number,
              callsign         = EXCLUDED.callsign,
              aircraft_type    = EXCLUDED.aircraft_type,
              registration     = EXCLUDED.registration,
              origin_icao      = EXCLUDED.origin_icao,
              destination_icao = EXCLUDED.destination_icao,
              actual_takeoff   = EXCLUDED.actual_takeoff,
              actual_landed    = EXCLUDED.actual_landed,
              fetched_at       = now()
            RETURNING id
            """,
            (
                summary.get("fr24_id"),
                summary.get("flight"),
                summary.get("callsign"),
                summary.get("type"),
                summary.get("reg"),
                summary.get("orig_icao"),
                summary.get("dest_icao"),
                summary.get("datetime_takeoff"),
                summary.get("datetime_landed"),
                target_date,
            ),
        )
        row = cur.fetchone()
        return row[0] if row else None


def upsert_track_points(conn, track_id: int, points: List[Dict]):
    """Delete old points and insert fresh ones."""
    with conn.cursor() as cur:
        cur.execute("DELETE FROM fr24_ext.track_points_fr24 WHERE track_id = %s", (track_id,))
        if not points:
            return
        args = [
            (
                track_id,
                p.get("timestamp"),
                p.get("lat"),
                p.get("lon"),
                p.get("alt"),
                p.get("gspeed"),
                p.get("vspeed"),
                p.get("track"),
                p.get("squawk"),
                p.get("source"),
            )
            for p in points
            if p.get("lat") is not None and p.get("lon") is not None
        ]
        psycopg2.extras.execute_values(
            cur,
            """
            INSERT INTO fr24_ext.track_points_fr24
              (track_id, observed_at, lat, lon, altitude_ft, gspeed_kt,
               vspeed_fpm, heading, squawk, source)
            VALUES %s
            """,
            args,
        )


def run(target_date: date, conn) -> Dict:
    """Main entry: load all tracks for target_date. Returns stats dict."""
    log.info("FR24 tracks: starting for %s", target_date)
    stats = {"date": str(target_date), "flights_found": 0, "tracks_loaded": 0, "errors": 0}

    summaries = list(iter_flight_summaries(target_date))
    stats["flights_found"] = len(summaries)
    log.info("FR24 tracks: found %d flights in summary", len(summaries))

    for summary in summaries:
        fr24_id = summary.get("fr24_id")
        if not fr24_id:
            continue
        try:
            track_id = upsert_flight(conn, summary, target_date)
            if track_id is None:
                continue

            points = fetch_track(fr24_id)
            if points is None:
                stats["errors"] += 1
                continue

            upsert_track_points(conn, track_id, points)
            conn.commit()
            stats["tracks_loaded"] += 1
            log.debug("FR24: %s → %d points", fr24_id, len(points))
        except Exception as e:
            conn.rollback()
            stats["errors"] += 1
            log.error("FR24: error processing %s: %s", fr24_id, e)

    log.info("FR24 tracks done: %s", stats)
    return stats
