"""
FR24 API Service
Minimal Flask API reading from PostgreSQL fr24 schema.
"""
import os
import time
import logging
from datetime import datetime, timezone
from functools import wraps

import psycopg2
import psycopg2.extras
from flask import Flask, jsonify, request

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [api] %(levelname)s %(message)s",
    datefmt="%Y-%m-%dT%H:%M:%S",
)
log = logging.getLogger("api")

app = Flask(__name__)

DB_DSN = (
    f"host={os.environ['POSTGRES_HOST']} "
    f"port={os.environ.get('POSTGRES_PORT', 5432)} "
    f"dbname={os.environ['POSTGRES_DB']} "
    f"user={os.environ['POSTGRES_USER']} "
    f"password={os.environ['POSTGRES_PASSWORD']}"
)
API_PORT = int(os.environ.get("API_PORT", 8080))
HEALTHCHECK_FILE = "/tmp/api-ready"
START_TIME = datetime.now(timezone.utc)

# ── db connection (simple persistent conn with reconnect) ─────────────────────

_conn = None

def get_conn():
    global _conn
    if _conn is None or _conn.closed:
        _conn = psycopg2.connect(DB_DSN)
        psycopg2.extras.register_uuid(_conn)
        log.info("DB connection established")
    return _conn


def query(sql: str, params=None) -> list:
    for attempt in range(2):
        try:
            conn = get_conn()
            with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
                cur.execute(sql, params)
                return [dict(r) for r in cur.fetchall()]
        except psycopg2.OperationalError:
            global _conn
            _conn = None
            if attempt == 1:
                raise


def query_one(sql: str, params=None) -> dict | None:
    rows = query(sql, params)
    return rows[0] if rows else None


# ── serialisation helper ──────────────────────────────────────────────────────

def serial(obj):
    """Make psycopg2 types JSON-serialisable."""
    import decimal, uuid
    if isinstance(obj, (datetime,)):
        return obj.isoformat()
    if isinstance(obj, decimal.Decimal):
        return float(obj)
    if isinstance(obj, uuid.UUID):
        return str(obj)
    raise TypeError(f"Not serialisable: {type(obj)}")


def ok(data, **kwargs):
    return app.response_class(
        __import__("json").dumps(data, default=serial),
        mimetype="application/json",
        **kwargs,
    )


def err(msg: str, status: int = 500):
    return ok({"error": msg}, status=status)


# ── routes ────────────────────────────────────────────────────────────────────

@app.get("/health")
def health():
    try:
        query_one("SELECT 1")
        db_ok = True
    except Exception as e:
        db_ok = False
    return ok({
        "status": "ok" if db_ok else "degraded",
        "db": "ok" if db_ok else "error",
        "uptime_seconds": int((datetime.now(timezone.utc) - START_TIME).total_seconds()),
    }, status=200 if db_ok else 503)


@app.get("/dashboard/status")
def dashboard_status():
    try:
        captures = query_one("SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE status='active') AS active FROM fr24.captures")
        packets  = query_one("SELECT COUNT(*) AS total FROM fr24.raw_packets")
        state    = query_one("SELECT state_value FROM fr24.processing_state WHERE state_key='preprocess_cursor'")
        aircraft = query_one("SELECT COUNT(*) AS total FROM fr24.aircraft")
        flights  = query_one("SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE status='active') AS active FROM fr24.flights")
        return ok({
            "captures": captures,
            "raw_packets": packets,
            "processing_state": state["state_value"] if state else None,
            "aircraft": aircraft,
            "flights": flights,
        })
    except Exception as e:
        return err(str(e))


@app.get("/viewer/config")
def viewer_config():
    return ok({
        "system": "fr24-ingest",
        "version": "0.1.0-scaffold",
        "stage": "step-1-fake-data",
        "db_schema": "fr24",
        "center": {"lat": 52.0, "lon": 27.0},
        "zoom": 6,
        "features": {
            "adsb_decode": False,
            "real_rtlsdr": False,
            "noise_model": False,
        },
    })


@app.get("/captures")
def captures():
    try:
        limit = min(int(request.args.get("limit", 50)), 200)
        rows = query(
            """
            SELECT capture_id, started_at, ended_at, source, device_index,
                   center_frequency_hz, sample_rate_hz, gain_db, status, notes, created_at
            FROM fr24.captures
            ORDER BY started_at DESC
            LIMIT %s
            """,
            (limit,),
        )
        return ok({"captures": rows, "count": len(rows)})
    except Exception as e:
        return err(str(e))


@app.get("/aircraft")
def aircraft():
    try:
        limit = min(int(request.args.get("limit", 100)), 500)
        rows = query(
            """
            SELECT aircraft_id, icao24, callsign, registration, aircraft_type,
                   operator_name, first_seen_at, last_seen_at
            FROM fr24.aircraft
            ORDER BY last_seen_at DESC NULLS LAST
            LIMIT %s
            """,
            (limit,),
        )
        return ok({"aircraft": rows, "count": len(rows)})
    except Exception as e:
        return err(str(e))


@app.get("/flights")
def flights():
    try:
        limit = min(int(request.args.get("limit", 100)), 500)
        status_filter = request.args.get("status")
        if status_filter:
            rows = query(
                """
                SELECT f.flight_id, f.aircraft_id, a.icao24, f.callsign,
                       f.departure_airport, f.arrival_airport,
                       f.started_at, f.ended_at, f.status, f.source
                FROM fr24.flights f
                JOIN fr24.aircraft a USING (aircraft_id)
                WHERE f.status = %s
                ORDER BY f.started_at DESC
                LIMIT %s
                """,
                (status_filter, limit),
            )
        else:
            rows = query(
                """
                SELECT f.flight_id, f.aircraft_id, a.icao24, f.callsign,
                       f.departure_airport, f.arrival_airport,
                       f.started_at, f.ended_at, f.status, f.source
                FROM fr24.flights f
                JOIN fr24.aircraft a USING (aircraft_id)
                ORDER BY f.started_at DESC
                LIMIT %s
                """,
                (limit,),
            )
        return ok({"flights": rows, "count": len(rows)})
    except Exception as e:
        return err(str(e))


# ── startup ───────────────────────────────────────────────────────────────────

def wait_for_db(max_attempts: int = 30):
    for attempt in range(1, max_attempts + 1):
        try:
            get_conn()
            return
        except psycopg2.OperationalError as e:
            log.warning("DB not ready (%d/%d): %s", attempt, max_attempts, e)
            time.sleep(2)
    log.error("Could not connect to DB")
    raise SystemExit(1)


if __name__ == "__main__":
    wait_for_db()
    open(HEALTHCHECK_FILE, "w").close()
    log.info("Healthcheck file written: %s", HEALTHCHECK_FILE)
    log.info("Starting API on port %d", API_PORT)
    app.run(host="0.0.0.0", port=API_PORT, debug=False)
