"""
Compatibility wrapper around JeanExtreme002/FlightRadarAPI.

The existing web UI already expected a utilities.fr24_client.FR24Client class.
This module fills that gap and returns data in the shapes used by web/app.py
and utilities/routelookup.py.
"""

import logging
import json
import os
import re
from time import time


logging.getLogger("FlightRadarAPI.request").setLevel(logging.ERROR)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
AIRLINES_FILE = os.path.join(BASE_DIR, "airlines.json")


def _nested(data, *keys):
    cur = data or {}
    for key in keys:
        if not isinstance(cur, dict):
            return None
        cur = cur.get(key)
    return cur


def _airport_pos(details, side, field):
    return _nested(details, "airport", side, "position", field)


def _time_value(details, group, field):
    return _nested(details, "time", group, field)


def _flight_identifiers(flight):
    values = [
        getattr(flight, "callsign", ""),
        getattr(flight, "number", ""),
        getattr(flight, "flight", ""),
        getattr(flight, "flight_number", ""),
        getattr(flight, "flight_code", ""),
        getattr(flight, "ident", ""),
        getattr(flight, "registration", ""),
    ]
    try:
        details = getattr(flight, "flight_details", None) or {}
        if isinstance(details, dict):
            values.extend([
                _nested(details, "identification", "callsign"),
                _nested(details, "identification", "number", "default"),
                _nested(details, "identification", "number", "alternative"),
            ])
    except Exception:
        pass
    return {(value or "").strip().upper() for value in values if value}


def _airline_aliases(callsign):
    match = re.match(r"^([A-Z]+)", (callsign or "").strip().upper())
    if not match:
        return []

    prefix = match.group(1)
    aliases = [prefix]
    try:
        with open(AIRLINES_FILE, encoding="utf-8") as f:
            airlines = json.load(f)
        name = airlines.get(prefix)
        if name:
            wanted_lengths = [2] if len(prefix) >= 3 else [3]
            for wanted_length in wanted_lengths:
                for code, airline_name in airlines.items():
                    if airline_name == name and len(code) == wanted_length and code not in aliases:
                        aliases.append(code)
                        break
    except Exception:
        pass

    return aliases


def _equivalent_callsigns(callsign):
    callsign = (callsign or "").strip().upper()
    match = re.match(r"^([A-Z]+)([A-Z0-9]+)$", callsign)
    if not match:
        return [callsign] if callsign else []

    suffix = match.group(2)
    values = [callsign]
    for alias in _airline_aliases(callsign):
        value = alias + suffix
        if value not in values:
            values.append(value)
    return values


def _search_items(data):
    if isinstance(data, list):
        for item in data:
            if isinstance(item, dict):
                yield item
    elif isinstance(data, dict):
        if any(key in data for key in ("id", "label", "detail", "callsign", "flight", "number")):
            yield data
        for value in data.values():
            yield from _search_items(value)


def _search_identifiers(item):
    def string_values(value, depth=0):
        if depth > 3:
            return
        if isinstance(value, str):
            yield value
        elif isinstance(value, dict):
            for nested in value.values():
                yield from string_values(nested, depth + 1)
        elif isinstance(value, list):
            for nested in value[:20]:
                yield from string_values(nested, depth + 1)

    identifiers = {(value or "").strip().upper() for value in string_values(item)}
    for value in list(identifiers):
        compact = re.sub(r"[^A-Z0-9]", "", value)
        if compact:
            identifiers.add(compact)
        identifiers.update(re.findall(r"\b[A-Z]{2,3}[0-9][A-Z0-9]*\b", value))
    return {value for value in identifiers if value}


def _as_float(value):
    try:
        if value is None or value == "":
            return None
        return float(value)
    except (TypeError, ValueError):
        return None


def _first_value(data, *keys):
    if not isinstance(data, dict):
        return None
    for key in keys:
        value = data.get(key)
        if value is not None and value != "":
            return value
    return None


def _position_from_item(item):
    if not isinstance(item, dict):
        return None
    detail = item.get("detail") if isinstance(item.get("detail"), dict) else {}
    for source in (item, detail):
        lat = _as_float(_first_value(source, "lat", "latitude"))
        lon = _as_float(_first_value(source, "lon", "lng", "longitude"))
        if lat is not None and lon is not None:
            return {
                "latitude": lat,
                "longitude": lon,
                "altitude": _first_value(source, "alt", "altitude") or 0,
                "ground_speed": _first_value(source, "spd", "speed", "ground_speed") or 0,
                "heading": _first_value(source, "hd", "heading", "track") or 0,
                "vertical_speed": _first_value(source, "vs", "vertical_speed") or 0,
            }
    return None


def _search_item_summary(item, targets=None):
    detail = item.get("detail") if isinstance(item, dict) and isinstance(item.get("detail"), dict) else {}
    identifiers = sorted(_search_identifiers(item))
    target_set = set(targets or [])
    position = _position_from_item(item)
    return {
        "matched": bool(target_set.intersection(identifiers)) if target_set else False,
        "identifiers": identifiers[:30],
        "id": _first_value(item, "id", "flight_id") or _first_value(detail, "id", "flight_id"),
        "label": _first_value(item, "label", "name", "description"),
        "detail": {
            "callsign": _first_value(detail, "callsign"),
            "flight": _first_value(detail, "flight", "flight_number", "number"),
            "route": _first_value(detail, "route"),
            "type": _first_value(detail, "type"),
        },
        "position": position,
        "keys": sorted(list(item.keys()))[:20] if isinstance(item, dict) else [],
    }


def _latest_trail_point(details):
    trail = details.get("trail", []) if isinstance(details, dict) else []
    points = [point for point in trail if isinstance(point, dict)]
    if not points:
        return None
    return max(points, key=lambda point: point.get("ts", point.get("timestamp", 0)) or 0)


class FR24Client:
    def __init__(self, timeout=20):
        from FlightRadarAPI import FlightRadar24API

        self._api = FlightRadar24API(timeout=timeout)

    @property
    def ok(self):
        return True

    def _run_with_client(self, callback):
        return callback(self._api)

    def _candidate_flights(self, callsign, plane_lat=None, plane_lon=None):
        callsign = (callsign or "").strip().upper()
        airlines = _airline_aliases(callsign)

        queries = []
        if plane_lat is not None and plane_lon is not None:
            try:
                bounds = self._api.get_bounds_by_point(float(plane_lat), float(plane_lon), 250000)
                for airline in airlines:
                    queries.append({"bounds": bounds, "airline": airline})
                queries.append({"bounds": bounds})
            except Exception:
                pass
        for airline in airlines:
            queries.append({"airline": airline})
        queries.append({})

        seen = set()
        seen_queries = set()
        for params in queries:
            clean = {k: v for k, v in params.items() if v}
            query_key = tuple(sorted(clean.items()))
            if query_key in seen_queries:
                continue
            seen_queries.add(query_key)
            try:
                for flight in self._api.get_flights(**clean):
                    fid = getattr(flight, "id", None)
                    if fid in seen:
                        continue
                    seen.add(fid)
                    yield flight
            except Exception as e:
                print(f"[FR24_API] get_flights failed for {clean}: {e}")

    def _find_by_callsign_async(self, fr24, callsign):
        # The old caller used this async-sounding name; keep it sync-compatible.
        del fr24
        targets = set(_equivalent_callsigns(callsign))
        matches = []
        for flight in self._candidate_flights(callsign):
            if targets.intersection(_flight_identifiers(flight)):
                matches.append(flight)
        return matches

    def _find_one(self, callsign, plane_lat=None, plane_lon=None):
        targets = set(_equivalent_callsigns(callsign))
        for flight in self._candidate_flights(callsign, plane_lat, plane_lon):
            if targets.intersection(_flight_identifiers(flight)):
                return flight
        return None

    def _search_tracked_flight(self, callsign):
        targets = set(_equivalent_callsigns(callsign))
        for query in targets:
            try:
                results = self._api.search(query, limit=50)
            except Exception as e:
                print(f"[FR24_API] direct search failed for {query}: {e}")
                continue

            for item in _search_items(results):
                identifiers = _search_identifiers(item)
                if not targets.intersection(identifiers):
                    continue
                detail = item.get("detail") if isinstance(item.get("detail"), dict) else {}
                flight_id = item.get("id") or detail.get("id") or detail.get("flight_id")
                if not flight_id:
                    continue

                proxy = type("_FR24FlightProxy", (), {
                    "id": flight_id,
                    "set_flight_details": lambda self, details: setattr(self, "flight_details", details),
                })()
                try:
                    details = self._api.get_flight_details(proxy)
                except Exception as e:
                    print(f"[FR24_API] direct details failed for {query}/{flight_id}: {e}")
                    details = {}

                point = _latest_trail_point(details)
                item_position = _position_from_item(item)
                if point:
                    position = {
                        "latitude": point.get("lat", point.get("latitude")),
                        "longitude": point.get("lng", point.get("lon", point.get("longitude"))),
                        "altitude": point.get("alt", point.get("altitude", 0)) or 0,
                        "ground_speed": point.get("spd", point.get("speed", 0)) or 0,
                        "heading": point.get("hd", point.get("heading", 0)) or 0,
                        "vertical_speed": point.get("vs", point.get("vertical_speed", 0)) or 0,
                    }
                elif item_position:
                    position = item_position
                else:
                    continue

                identification = details.get("identification", {})
                number_info = identification.get("number", {}) if isinstance(identification, dict) else {}
                airport = details.get("airport", {})
                origin = airport.get("origin", {}) if isinstance(airport, dict) else {}
                destination = airport.get("destination", {}) if isinstance(airport, dict) else {}
                airline = details.get("airline", {})
                aircraft = details.get("aircraft", {})
                model = aircraft.get("model", {}) if isinstance(aircraft, dict) else {}
                airline_code = airline.get("code", {}) if isinstance(airline, dict) else {}
                return {
                    "callsign": (
                        identification.get("callsign")
                        or detail.get("callsign")
                        or callsign
                    ),
                    "number": (
                        number_info.get("default")
                        or number_info.get("alternative")
                        or detail.get("flight")
                        or callsign
                    ),
                    "airline_name": airline.get("name", "") if isinstance(airline, dict) else "",
                    "airline_icao": airline_code.get("icao", "") if isinstance(airline_code, dict) else "",
                    "airline_iata": airline_code.get("iata", "") if isinstance(airline_code, dict) else "",
                    "is_live": True,
                    "origin": origin.get("code", {}).get("iata", "") if isinstance(origin.get("code"), dict) else "",
                    "destination": destination.get("code", {}).get("iata", "") if isinstance(destination.get("code"), dict) else "",
                    "origin_latitude": _airport_pos(details, "origin", "latitude"),
                    "origin_longitude": _airport_pos(details, "origin", "longitude"),
                    "destination_latitude": _airport_pos(details, "destination", "latitude"),
                    "destination_longitude": _airport_pos(details, "destination", "longitude"),
                    "dest_lat": _airport_pos(details, "destination", "latitude"),
                    "dest_lon": _airport_pos(details, "destination", "longitude"),
                    "aircraft_type": model.get("code", "") if isinstance(model, dict) else "",
                    "altitude": position.get("altitude", 0) or 0,
                    "ground_speed": position.get("ground_speed", 0) or 0,
                    "heading": position.get("heading", 0) or 0,
                    "vertical_speed": position.get("vertical_speed", 0) or 0,
                    "latitude": position.get("latitude"),
                    "longitude": position.get("longitude"),
                    "position_source": "FR24UnofficialSearch",
                    "source_seen": 0,
                    "source_seen_pos": 0,
                    "time_scheduled_departure": _time_value(details, "scheduled", "departure"),
                    "time_scheduled_arrival": _time_value(details, "scheduled", "arrival"),
                    "time_real_departure": _time_value(details, "real", "departure"),
                    "time_estimated_arrival": (
                        _time_value(details, "estimated", "arrival")
                        or _time_value(details, "real", "arrival")
                    ),
                    "trail": details.get("trail", []),
                }
        return None

    def debug_search(self, callsign, limit=20):
        targets = set(_equivalent_callsigns(callsign))
        output = {"targets": sorted(targets), "items": []}
        try:
            try:
                results = self._api.search(callsign, limit=50)
            except TypeError:
                results = self._api.search(callsign)
        except Exception as e:
            output["error"] = str(e)
            return output

        output["payload_type"] = type(results).__name__
        if isinstance(results, dict):
            output["payload_keys"] = sorted([str(key) for key in results.keys()])[:20]

        for item in _search_items(results):
            summary = _search_item_summary(item, targets)
            if summary["matched"] or len(output["items"]) < limit:
                output["items"].append(summary)
            if summary["matched"]:
                break
        output["item_count_shown"] = len(output["items"])
        return output

    def get_flight_details(self, flight_or_callsign, plane_lat=None, plane_lon=None,
                           plane_type="", registration="", distance=0.0):
        del distance
        if isinstance(flight_or_callsign, str):
            print(f"[FR24_API] {flight_or_callsign}")
            flight = self._find_one(flight_or_callsign, plane_lat, plane_lon)
            if not flight:
                return {}
        else:
            flight = flight_or_callsign

        details = {}
        try:
            details = self._api.get_flight_details(flight)
            flight.set_flight_details(details)
        except Exception as e:
            print(f"[FR24_API] details failed for {getattr(flight, 'callsign', '')}: {e}")

        origin = getattr(flight, "origin_airport_iata", None) or getattr(flight, "origin_airport_icao", None)
        dest = (
            getattr(flight, "destination_airport_iata", None)
            or getattr(flight, "destination_airport_icao", None)
        )

        sched_dep = _time_value(details, "scheduled", "departure")
        sched_arr = _time_value(details, "scheduled", "arrival")
        real_dep = _time_value(details, "real", "departure") or _time_value(details, "estimated", "departure")
        est_arr = _time_value(details, "estimated", "arrival") or _time_value(details, "real", "arrival")

        route = {
            "airline_name": (
                getattr(flight, "airline_name", None)
                or getattr(flight, "airline_short_name", None)
                or getattr(flight, "airline_icao", "")
                or ""
            ),
            "airline_icao": getattr(flight, "airline_icao", "") or "",
            "airline_iata": getattr(flight, "airline_iata", "") or "",
            "origin_iata": origin or "",
            "origin_lat": (
                getattr(flight, "origin_airport_latitude", None)
                or _airport_pos(details, "origin", "latitude")
            ),
            "origin_lon": (
                getattr(flight, "origin_airport_longitude", None)
                or _airport_pos(details, "origin", "longitude")
            ),
            "dest_iata": dest or "",
            "dest_lat": (
                getattr(flight, "destination_airport_latitude", None)
                or _airport_pos(details, "destination", "latitude")
            ),
            "dest_lon": (
                getattr(flight, "destination_airport_longitude", None)
                or _airport_pos(details, "destination", "longitude")
            ),
            "plane_type": getattr(flight, "aircraft_code", None) or plane_type or "",
            "time_scheduled_departure": sched_dep,
            "time_scheduled_arrival": sched_arr,
            "time_real_departure": real_dep,
            "time_estimated_arrival": est_arr,
            "trail": details.get("trail", []) if isinstance(details, dict) else [],
            "schedule_info": {
                "scheduled_departure": sched_dep,
                "scheduled_arrival": sched_arr,
                "actual_departure": real_dep,
            },
            "flight_progress": {
                "eta": est_arr,
            },
            "raw": details,
        }

        if not route["origin_lat"] and origin:
            try:
                from utilities.airports import get_airport_coords
                coords = get_airport_coords(origin)
                route["origin_lat"] = coords.get("lat")
                route["origin_lon"] = coords.get("lon")
            except Exception:
                pass

        if not route["dest_lat"] and dest:
            try:
                from utilities.airports import get_airport_coords
                coords = get_airport_coords(dest)
                route["dest_lat"] = coords.get("lat")
                route["dest_lon"] = coords.get("lon")
            except Exception:
                pass

        return route

    def get_tracked_flight(self, callsign):
        print(f"[FR24_API] tracked {callsign}")
        flight = self._find_one(callsign)
        if not flight:
            return self._search_tracked_flight(callsign)

        route = self.get_flight_details(flight)
        return {
            "callsign": callsign,
            "number": getattr(flight, "number", None) or callsign,
            "airline_name": route.get("airline_name", ""),
            "is_live": not bool(getattr(flight, "on_ground", False)),
            "origin": route.get("origin_iata", ""),
            "destination": route.get("dest_iata", ""),
            "dest_lat": route.get("dest_lat"),
            "dest_lon": route.get("dest_lon"),
            "origin_latitude": route.get("origin_lat"),
            "origin_longitude": route.get("origin_lon"),
            "destination_latitude": route.get("dest_lat"),
            "destination_longitude": route.get("dest_lon"),
            "aircraft_type": route.get("plane_type", ""),
            "altitude": getattr(flight, "altitude", 0) or 0,
            "ground_speed": getattr(flight, "ground_speed", 0) or 0,
            "heading": getattr(flight, "heading", 0) or 0,
            "vertical_speed": getattr(flight, "vertical_speed", 0) or 0,
            "latitude": getattr(flight, "latitude", None),
            "longitude": getattr(flight, "longitude", None),
            "position_source": "FR24Unofficial",
            "time_scheduled_departure": route.get("time_scheduled_departure"),
            "time_scheduled_arrival": route.get("time_scheduled_arrival"),
            "time_real_departure": route.get("time_real_departure"),
            "time_estimated_arrival": route.get("time_estimated_arrival") or time(),
        }
