import datetime
import json
import time
from concurrent.futures import ThreadPoolExecutor
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 (vähentää API-kuormaa)
ilmatila_data = {"koneet": [], "paivitetty": "Ei vielä päivitetty", "status": "Käynnistetään..."}
viimeisimmät_icao_tunnukset = set()
malli_vallimuisti = {}

from datetime import datetime, timedelta

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 datetime.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 = datetime.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. Välimuistittaa tuloksen."""
    if icao24 in malli_vallimuisti:
        return malli_vallimuisti[icao24]

    try:
        # Haetaan yksittäisen koneen tarkemmat tiedot
        res = requests.get(f"{METADATA_URL}{icao24}", auth=(OPENSKY_USER, OPENSKY_PASS), 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")
        
        # 1. Haetaan tuore token
        token = hae_access_token()
        
        if not token:
            ilmatila_data["status"] = "Tunnistautumisvirhe: Tokenia ei saatu."
            print(f"[{nykyhetki}] ❌ Ei saatu tokenia. Odotetaan hetki...")
            time.sleep(15)
            continue

        # 2. Luodaan otsikko haulle
        headers = {
            "Authorization": f"Bearer {token}"
        }

        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

                    koneen_malli = "Ladataan..."
                    if icao24 in malli_vallimuisti:
                        koneen_malli = malli_vallimuisti[icao24]
                    else:
                        # Käytetään samaa tokenia myös metadatan hakuun, jos funktiosi sitä vaatii
                        koneen_malli = hae_koneen_malli(icao24)
                        time.sleep(1)

                    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}"

        # Pidetään sovittu 30 sekunnin väli
        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";
                
                // Piilotetaan FR24 nappi jos lennon tunnusta ei ole saatavilla
                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")

import ssl
from concurrent.futures import ThreadPoolExecutor

if __name__ == "__main__":
    executor = ThreadPoolExecutor(max_workers=1)
    executor.submit(paivita_ilmatila)
    cert_path = "/etc/letsencrypt/live/vibecoding.vaikkinen.info/fullchain.pem"
    key_path = "/etc/letsencrypt/live/vibecoding.vaikkinen.info/privkey.pem"
    print("Luodaan tiukka SSL-konteksti...")
    # Luodaan explicit-konteksti palvelinkäyttöön
    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=5000, ssl_context=context)

