import datetime
import json
import os
import ssl
import sys
import threading
import time
from datetime import datetime as dt, timedelta
from flask import Flask, Response, render_template_string
import requests

app = Flask(__name__)

TOKEN_URL = "https://auth.opensky-network.org/auth/realms/opensky-network/protocol/openid-connect/token"
CLIENT_ID = "vaikero-api-client"
CLIENT_SECRET = "0X5576uw9airrXsR4ORMkujDoT3J5N87"
TOKEN_REFRESH_MARGIN = 30
PARAMS = {"lamin": 60.23, "lamax": 62.03, "lomin": 19.58, "lomax": 23.42}
API_URL = "https://opensky-network.org/api/states/all"
METADATA_URL = "https://opensky-network.org/api/metadata/aircraft/icao/"

# Globaali välimuisti ilmatilan tilanteesta ja koneiden malleista
ilmatila_data = {"koneet": [], "paivitetty": "Ei vielä päivitetty", "status": "Käynnistetään..."}
viimeisimmät_icao_tunnukset = set()
malli_vallimuisti = {}


class TokenManager:
    def __init__(self):
        self.token = None
        self.expires_at = None

    def get_token(self):
        """Palauttaa voimassa olevan tokenin, hakee tarvittaessa uuden automaattisesti."""
        if self.token and self.expires_at and dt.now() < self.expires_at:
            return self.token
        return self._refresh()

    def _refresh(self):
        """Hakee uuden access tokenin OpenSkyn palvelimelta."""
        r = requests.post(
            TOKEN_URL,
            data={
                "grant_type": "client_credentials",
                "client_id": CLIENT_ID,
                "client_secret": CLIENT_SECRET,
            },
            timeout=10
        )
        r.raise_for_status()
        data = r.json()
        self.token = data["access_token"]
        expires_in = data.get("expires_in", 1800)
        self.expires_at = dt.now() + timedelta(seconds=expires_in - TOKEN_REFRESH_MARGIN)
        return self.token

    def headers(self):
        """Palauttaa valmiit otsikot Bearer-tokenilla."""
        return {"Authorization": f"Bearer {self.get_token()}"}


# Luodaan yksi jaettu manageri sovelluksen käyttöön
tokens = TokenManager()


def hae_koneen_malli(icao24):
    """Hakee koneen mallitiedot OpenSkyn metadatasta OAuth2-otsikoilla."""
    if icao24 in malli_vallimuisti:
        return malli_vallimuisti[icao24]

    try:
        # KÄYTETÄÄN TOKENIA: Vaihdettu vanha Basic Auth toimivaan OAuth2-headeriin
        res = requests.get(f"{METADATA_URL}{icao24}", headers=tokens.headers(), timeout=3)
        if res.status_code == 200:
            m_data = res.json()
            valmistaja = m_data.get("manufacturerName", "")
            malli = m_data.get("model", "")
            tyyppikoodi = m_data.get("typecode", "")

            if valmistaja or malli:
                tulos = f"{valmistaja} {malli} ({tyyppikoodi})".strip()
            else:
                tulos = "Tuntematon konetyyppi"

            malli_vallimuisti[icao24] = tulos
            return tulos
    except Exception:
        pass

    return "Ei saatavilla"


def paivita_ilmatila():
    """Taustafunktio, joka hakee datan OpenSkyltä OAuth2-tunnistautuneena."""
    global ilmatila_data, viimeisimmät_icao_tunnukset
    print("Taustaseuranta käynnistetty OAuth2-tunnistautumisella.")

    while True:
        nykyhetki = datetime.datetime.now().strftime("%H:%M:%S")

        try:
            # KORJAUS: Käytetään TokenManageria olemattoman hae_access_token()-funktion sijaan
            headers = tokens.headers()
        except Exception as e:
            ilmatila_data["status"] = "Tunnistautumisvirhe: Tokenia ei saatu."
            print(f"[{nykyhetki}] ❌ Ei saatu tokenia: {e}. Odotetaan hetki...")
            time.sleep(15)
            continue

        try:
            # Pääkysely ilmatilan tilasta virallisilla tunnuksilla
            response = requests.get(
                API_URL,
                params=PARAMS,
                headers=headers,
                timeout=10
            )

            if response.status_code == 200:
                data = response.json()
                states = data.get("states") or []

                nykyiset_koneet = []
                nykyiset_icaot = set()
                uusi_kone_havaittu = False

                for flight in states:
                    icao24 = flight[0]
                    callsign = flight[1].strip() if flight[1] else "EI TUNNUSTA"
                    country = flight[2]
                    altitude = flight[7]
                    velocity = flight[9]

                    alt_ft = int(altitude * 3.28084) if altitude else None
                    speed_kmh = int(velocity * 3.6) if velocity else None

                    nykyiset_icaot.add(icao24)

                    if icao24 not in viimeisimmät_icao_tunnukset:
                        uusi_kone_havaittu = True

                    if icao24 in malli_vallimuisti:
                        koneen_malli = malli_vallimuisti[icao24]
                    else:
                        koneen_malli = hae_koneen_malli(icao24)
                        time.sleep(0.5)  # Pieni viive, ettei paukuteta APIa liian lujaa

                    fr24_linkki = f"https://www.flightradar24.com/data/flights/{callsign.lower()}" if callsign != "EI TUNNUSTA" else "#"
                    adsb_linkki = f"https://globe.adsbexchange.com/?icao={icao24}"

                    nykyiset_koneet.append(
                        {
                            "icao": icao24,
                            "callsign": callsign,
                            "malli": koneen_malli,
                            "country": country,
                            "altitude": f"{alt_ft} ft (~{int(altitude)} m)" if alt_ft else "Ei saatavilla",
                            "speed": f"{speed_kmh} km/h" if speed_kmh else "Ei saatavilla",
                            "fr24": fr24_linkki,
                            "adsb": adsb_linkki
                        }
                    )

                viimeisimmät_icao_tunnukset = nykyiset_icaot
                ilmatila_data = {
                    "koneet": nykyiset_koneet,
                    "paivitetty": nykyhetki,
                    "status": "Yhteys kunnossa (OAuth2)",
                    "hälytys": uusi_kone_havaittu,
                }
                print(f"[{nykyhetki}] OpenSky päivitetty. Alueella {len(nykyiset_koneet)} konetta.")

            elif response.status_code == 429:
                ilmatila_data["status"] = "OpenSky virhe: 429 (Liikaa kyselyitä). Odotetaan..."
                print(f"[{nykyhetki}] ⚠️ Palvelin palautti 429 tunnuksista huolimatta. Otetaan aikalisä.")
                time.sleep(30)
            else:
                ilmatila_data["status"] = f"OpenSky virhe: {response.status_code}."
                print(f"[{nykyhetki}] Virhe rajapinnassa: {response.status_code}")
        except Exception as e:
            ilmatila_data["status"] = f"Yhteysvirhe: {e}"
            print(f"[{nykyhetki}] Poikkeus taustalla: {e}")

        time.sleep(30)


HTML_SIVU = """
<!DOCTYPE html>
<html lang="fi">
<head>
    <meta charset="UTF-8">
    <title>Rauman Ilmatilavahti (100km)</title>
    <style>
        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0f172a; color: #f8fafc; max-width: 800px; margin: 40px auto; padding: 0 20px; }
        h1 { color: #38bdf8; border-bottom: 2px solid #334155; padding-bottom: 10px; }
        .meta-info { display: flex; justify-content: space-between; color: #94a3b8; font-size: 0.9em; margin-bottom: 20px; background: #1e293b; padding: 10px 15px; border-radius: 6px; }
        .kone-kortti { background: #1e293b; padding: 15px; margin-bottom: 10px; border-radius: 8px; border-left: 4px solid #38bdf8; display: grid; grid-template-columns: 1.2fr 1fr 0.8fr; gap: 10px; align-items: center; }
        .callsign { font-weight: bold; font-size: 1.3em; color: #fff; }
        .malli { font-size: 0.95em; color: #e2e8f0; margin-top: 2px; font-style: italic; }
        .tyhja { text-align: center; color: #64748b; padding: 40px; font-style: italic; background: #1e293b; border-radius: 8px; }
        #audio-lupa { background: #b91c1c; color: white; padding: 10px; text-align: center; font-weight: bold; margin-bottom: 20px; border-radius: 6px; cursor: pointer; display: block; }
        .linkit { display: flex; flex-direction: column; gap: 5px; text-align: right; }
        .nappi { display: inline-block; padding: 4px 8px; background: #334155; color: #38bdf8; text-decoration: none; border-radius: 4px; font-size: 0.8em; font-weight: bold; transition: background 0.2s; }
        .nappi:hover { background: #475569; color: #7dd3fc; }
    </style>
</head>
<body>

    <h1>✈️ Rauman Ilmatilavahti (100 km)</h1>

    <div id="audio-lupa" onclick="aktivoiAani()">⚠️ Napsauta tästä aktivoidaksesi äänihälytykset</div>

    <div class="meta-info">
        <div>Tila: <span id="status">Ladataan...</span></div>
        <div>Päivitetty viimeksi: <span id="aika">-</span></div>
    </div>

    <div id="koneet-lista">
        <div class="tyhja">Odotetaan ensimmäistä päivitystä...</div>
    </div>

    <script>
        let aanetSallittu = false;
        const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

        function aktivoiAani() {
            aanetSallittu = true;
            document.getElementById('audio-lupa').style.display = 'none';
            piippaa(800, 0.1);
        }

        function piippaa(taajuus, kesto) {
            if (!aanetSallittu) return;
            let osc = audioCtx.createOscillator();
            let gain = audioCtx.createGain();
            osc.connect(gain);
            gain.connect(audioCtx.destination);
            osc.frequency.value = taajuus;
            gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
            osc.start();
            osc.stop(audioCtx.currentTime + kesto);
        }

        const eventSource = new EventSource("/stream");

        eventSource.onmessage = function(event) {
            const data = JSON.parse(event.data);

            document.getElementById("status").innerText = data.status;
            document.getElementById("aika").innerText = data.paivitetty;

            if (data.hälytys) {
                piippaa(523.25, 0.15);
                setTimeout(() => piippaa(659.25, 0.2), 200);
            }

            const listaElementti = document.getElementById("koneet-lista");
            listaElementti.innerHTML = "";

            if (data.koneet.length === 0) {
                listaElementti.innerHTML = '<div class="tyhja">Ilmatila on puhdas. Ei koneita 100km säteellä.</div>';
                return;
            }

            data.koneet.forEach(kone => {
                const kortti = document.createElement("div");
                kortti.className = "kone-kortti";

                const fr24Nappi = kone.fr24 !== "#" ? `<a href="${kone.fr24}" target="_blank" class="nappi">Flightradar24 ↗</a>` : '';

                kortti.innerHTML = `
                    <div>
                        <div class="callsign">${kone.callsign}</div>
                        <div class="malli">${kone.malli}</div>
                        <div style="font-size: 0.75em; color: #64748b; margin-top:4px;">ICAO: ${kone.icao} (${kone.country})</div>
                    </div>
                    <div>
                        <div style="font-size: 0.9em;">Korkeus: ${kone.altitude}</div>
                        <div style="font-size: 0.9em; margin-top: 3px;">Nopeus: ${kone.speed}</div>
                    </div>
                    <div class="linkit">
                        ${fr24Nappi}
                        <a href="${kone.adsb}" target="_blank" class="nappi">ADS-B Globe ↗</a>
                    </div>
                `;
                listaElementti.appendChild(kortti);
            });
        };
    </script>
</body>
</html>
"""


@app.route("/")
def index():
    return render_template_string(HTML_SIVU)


@app.route("/stream")
def stream():
    def event_stream():
        viimeisin_paivitys = ""
        while True:
            if ilmatila_data["paivitetty"] != viimeisin_paivitys:
                yield f"data: {json.dumps(ilmatila_data)}\n\n"
                viimeisin_paivitys = ilmatila_data["paivitetty"]
                ilmatila_data["hälytys"] = False
            time.sleep(1)

    return Response(event_stream(), mimetype="text/event-stream")


if __name__ == "__main__":
    # KORJAUS: Käynnistetään taustasäie tavallisena daemon-threadina executorin sijaan. Varmempaa Flaskin kanssa.
    t = threading.Thread(target=paivita_ilmatila, daemon=True)
    t.start()

    cert_path = "/etc/letsencrypt/live/vibecoding.vaikkinen.info/fullchain.pem"
    key_path = "/etc/letsencrypt/live/vibecoding.vaikkinen.info/privkey.pem"

    if not os.path.exists(cert_path) or not os.path.exists(key_path):
        print(f"[VIRHE] Sertifikaatteja ei löydy polusta! Aja sudo-oikeuksilla.", file=sys.stderr)
        sys.exit(1)

    print("Luodaan tiukka SSL-konteksti...")
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    context.load_cert_chain(certfile=cert_path, keyfile=key_path)

    print("Pakotetaan HTTPS-tila lokeissa näkyvälle domainille...")
    app.run(debug=False, host="0.0.0.0", port=443, ssl_context=context)
