#!/usr/bin/python3
import subprocess
import os
import sys
import json
import shutil
import threading
import gc
import time


def _valid_json_file(path):
    try:
        with open(path, encoding="utf-8-sig") as f:
            return isinstance(json.load(f), dict)
    except Exception:
        return False


def _migrate_legacy_factory_defaults(cfg_path, cfg_backup):
    """Upgrade the original Chicago factory profile without touching custom setups."""
    try:
        with open(cfg_path, encoding="utf-8-sig") as f:
            cfg = json.load(f)
    except Exception:
        return False

    location = cfg.get("location", {})
    zone = location.get("zone_home", {})
    home = location.get("location_home", [])
    is_legacy_factory_profile = (
        home == [41.92655, -87.69544]
        and zone == {
            "tl_y": 41.953371,
            "tl_x": -87.746,
            "br_y": 41.889507,
            "br_x": -87.623695,
        }
        and location.get("journey_code") == "ORD"
    )
    if not is_legacy_factory_profile:
        return False

    location.update({
        "zone_home": {
            "tl_y": 51.62411093481396,
            "tl_x": -0.7609263601605234,
            "br_y": 51.376220612336155,
            "br_x": -0.08815410328046185,
        },
        "location_home": [51.47225255067069, -0.4579334549242383],
        "temperature_location": "51.47225255067069, -0.4579334549242383",
        "temperature_units": "metric",
        "clock_format": "24hr",
        "journey_code": "LHR",
    })

    display = cfg.setdefault("display", {})
    display["panel_count"] = 1
    display["gpio_slowdown"] = 1

    position_sources = cfg.setdefault("position_sources", {})
    position_sources["order"] = ["OpenSky"]
    position_sources["poll_seconds"] = 60
    position_sources["enabled"] = {"OpenSky": True}

    api_sources = cfg.setdefault("api_sources", {})
    api_sources["order"] = [
        "FlightStats",
        "AirLabs",
        "FlightAware",
        "FR24Unofficial",
        "FR24",
    ]
    api_sources["enabled"] = {
        "FlightStats": True,
        "AirLabs": False,
        "FlightAware": False,
        "FR24Unofficial": False,
        "FR24": False,
    }

    tmp_path = cfg_path + ".tmp"
    with open(tmp_path, "w", encoding="utf-8") as f:
        json.dump(cfg, f, indent=2)
        f.flush()
        os.fsync(f.fileno())
    os.replace(tmp_path, cfg_path)
    shutil.copy2(cfg_path, cfg_backup)
    print("[Setup] Migrated legacy Chicago factory defaults to Heathrow")
    return True


def _ensure_config():
    """
    Create default config files on first run if they don't exist.
    This lets the web UI load even before the user has configured anything.
    """
    base_dir = os.path.dirname(os.path.abspath(__file__))
    cfg_dir  = os.path.join(base_dir, "config")
    os.makedirs(cfg_dir, exist_ok=True)

    cfg_path = os.path.join(cfg_dir, "config.json")
    sec_path = os.path.join(cfg_dir, "secrets.json")
    cfg_backup = cfg_path + ".bak"
    sec_backup = sec_path + ".bak"

    default_cfg = {
        "location": {
            "zone_home": {
                "tl_y": 51.62411093481396,
                "tl_x": -0.7609263601605234,
                "br_y": 51.376220612336155,
                "br_x": -0.08815410328046185
            },
            "location_home": [51.47225255067069, -0.4579334549242383],
            "temperature_location": "51.47225255067069, -0.4579334549242383",
            "temperature_units": "metric",
            "distance_units": "imperial",
            "speed_units": "imperial",
            "clock_format": "24hr",
            "journey_code": "LHR",
            "journey_blank_filler": " ? "
        },
        "display": {
            "brightness": 100,
            "brightness_night": 50,
            "night_brightness": False,
            "panel_count": 1,
            "night_start": "22:00",
            "night_end": "06:00",
            "gpio_slowdown": 1,
            "hat_pwm_enabled": True,
            "forecast_days": 3
        },
        "flights": {
            "min_altitude": 2600,
            "max_altitude": 100000,
            "max_display_flights": 5,
            "max_farthest": 3,
            "max_closest": 3,
            "email": ""
        },
        "master_slave": {
            "master_tracker": "",
            "other_tracker_hostnames": []
        },
        "route_cache": {
            "enabled": True,
            "days": 30
        },
        "quiet_time": {
            "enabled": False,
            "start": "23:00",
            "end": "06:00",
            "weather_interval_seconds": 3600
        },
        "updates": {
            "enabled": False,
            "manifest_url": "",
            "check_on_start": True,
            "auto_install": True,
            "timeout_seconds": 20,
            "keep_backups": 2
        },
        "position_sources": {
            "order": ["OpenSky"],
            "poll_seconds": 60,
            "enabled": {
                "OpenSky": True
            }
        },
        "api_sources": {
            "order": ["FlightStats", "AirLabs", "FlightAware", "FR24Unofficial", "FR24"],
            "enabled": {
                "FlightStats": True,
                "AirLabs": False,
                "FlightAware": False,
                "FR24Unofficial": False,
                "FR24": False
            }
        },
        "display_fields": {
            "flight_details": ["airline", "flight_number"],
            "plane_details": ["plane", "home_distance", "direction", "altitude", "vertical_speed"]
        },
        "sports": {
            "enabled": True,
            "priority": ["mlb", "nfl", "nba", "nhl", "mls",
                         "college_football", "college_basketball", "epl"],
            "teams": {},
            "cycle_seconds": 10
        }
    }

    default_sec = {
        "tomorrow_api_key": "",
        "opensky_client_id": "",
        "opensky_client_secret": "",
        "airlabs_api_keys": [],
        "flightaware_api_keys": [],
        "flightaware_monthly_limit": 4.00,
        "flightradar24_key": "",
        "moon_key": ""
    }

    if not _valid_json_file(cfg_path) and _valid_json_file(cfg_backup):
        shutil.copy2(cfg_backup, cfg_path)
        print("[Setup] Restored config/config.json from backup")

    if not _valid_json_file(sec_path) and _valid_json_file(sec_backup):
        shutil.copy2(sec_backup, sec_path)
        print("[Setup] Restored config/secrets.json from backup")

    if _valid_json_file(cfg_path):
        _migrate_legacy_factory_defaults(cfg_path, cfg_backup)

    if not _valid_json_file(cfg_path):
        with open(cfg_path, "w") as f:
            json.dump(default_cfg, f, indent=2)
        print("[Setup] Created default config/config.json — edit via web UI at :8080/config")

    if not _valid_json_file(sec_path):
        with open(sec_path, "w") as f:
            json.dump(default_sec, f, indent=2)
        print("[Setup] Created default config/secrets.json — add your API keys via web UI at :8080/config")


if __name__ == "__main__":
    base_dir = os.path.dirname(os.path.abspath(__file__))

    # Ensure config files exist before anything else loads
    _ensure_config()

    app_path = os.path.join(base_dir, "web", "app.py")
    setup_web_process = None
    startup_display = None
    try:
        from utilities.startup_status import StartupStatus
        from utilities.updater import prepare_startup, startup_update
        from utilities.wifi_setup_mode import (
            active_wifi_ip,
            connection_result,
            ensure_setup_ap_if_offline,
            home_wifi_connection,
            is_setup_active,
            start_setup_ap,
        )

        startup_display = StartupStatus()
        startup_display.show_splash(5)

        connected_wifi = home_wifi_connection()
        if not connected_wifi:
            startup_display.show_wifi_configure()
            setup_web_process = subprocess.Popen([sys.executable, app_path])
            setup_result = ensure_setup_ap_if_offline(wait_seconds=0)
            print(f"[WiFiSetup] {setup_result.get('message', '')}")
            if setup_result.get("active"):
                print(
                    f"[WiFiSetup] Join {setup_result.get('ssid')} with password "
                    f"{setup_result.get('password')} then open {setup_result.get('url')}"
                )
            last_setup_attempt = time.monotonic()
            connected_since = None
            while True:
                connected_wifi = home_wifi_connection()
                connected_result = connection_result()
                if connected_wifi:
                    if connected_since is None:
                        connected_since = time.monotonic()
                    connection_finished = connected_result.get("state") == "connected"
                    connection_stable = time.monotonic() - connected_since >= 5
                    if connection_finished or connection_stable:
                        break
                else:
                    connected_since = None
                result_age = time.time() - float(connected_result.get("updated_at") or 0)
                connection_in_progress = (
                    connected_result.get("state") == "starting"
                    and result_age < 90
                )
                if (
                    not connection_in_progress
                    and not is_setup_active()
                    and time.monotonic() - last_setup_attempt >= 15
                ):
                    setup_result = start_setup_ap()
                    print(f"[WiFiSetup] {setup_result.get('message', '')}")
                    last_setup_attempt = time.monotonic()
                time.sleep(2)

            setup_web_process.terminate()
            try:
                setup_web_process.wait(timeout=10)
            except subprocess.TimeoutExpired:
                setup_web_process.kill()
                setup_web_process.wait(timeout=5)
            setup_web_process = None

        ip_address = ""
        ip_deadline = time.monotonic() + 10
        while time.monotonic() < ip_deadline and not ip_address:
            ip_address = active_wifi_ip()
            if not ip_address:
                time.sleep(1)
        startup_display.show_wifi_connected(ip_address, 5)

        startup_display.show("Starting", 0)
        startup_prepare = prepare_startup(progress=startup_display.show)
        if startup_prepare.get("rolled_back"):
            startup_display.show("Restarting previous version", 100)
            startup_display.close()
            os.execv(sys.executable, [sys.executable, os.path.abspath(__file__)])
        update_result = startup_update(progress=startup_display.show)
        if update_result.get("installed"):
            startup_display.show("Restarting updated app", 100)
            startup_display.close()
            os.execv(sys.executable, [sys.executable, os.path.abspath(__file__)])
        startup_display.show("Starting display", 100)
        time.sleep(5)
    except Exception as e:
        print(f"[Updater] Startup check failed: {e}")
    finally:
        if setup_web_process is not None and setup_web_process.poll() is None:
            setup_web_process.terminate()
            try:
                setup_web_process.wait(timeout=10)
            except subprocess.TimeoutExpired:
                setup_web_process.kill()
        if startup_display is not None:
            startup_display.close(clear=False)
            startup_display = None
            gc.collect()

    # Start Flask server in background
    subprocess.Popen([sys.executable, app_path])

    # Give NetworkManager time to reconnect to a saved Wi-Fi network. If it
    # cannot, start a temporary setup access point without delaying the display.
    def _start_wifi_setup_if_needed():
        try:
            from utilities.wifi_setup_mode import ensure_setup_ap_if_offline
            result = ensure_setup_ap_if_offline(wait_seconds=35)
            print(f"[WiFiSetup] {result.get('message', '')}")
            if result.get("active"):
                print(
                    f"[WiFiSetup] Join {result.get('ssid')} with password "
                    f"{result.get('password')} then open {result.get('url')}"
                )
        except Exception as e:
            print(f"[WiFiSetup] Could not start setup mode: {e}")

    threading.Thread(target=_start_wifi_setup_if_needed, daemon=True).start()

    # Start display loop
    from display import Display
    run_text = Display()
    try:
        from utilities.updater import mark_app_started
        mark_app_started()
    except Exception as e:
        print(f"[Updater] Could not mark startup healthy: {e}")
    run_text.run()
