"""
TAM ACARS Web - Agente Local v2.0
==================================
Captura completa dos parametros de voo via FSUIPC (MSFS/P3D/FSX) e envia
posicao em tempo real + log completo de voo (track + eventos + metricas)
para tamvirtual.com.br.

REQUISITOS:
  - Python 3.10+ (Windows)  |  o instalador ja cuida disso
  - FSUIPC7 (MSFS) ou FSUIPC5/6 (P3D/FSX)
  - pacotes 'fsuipc' (fornece o modulo pyuipc) + 'requests' (o instalador ja cuida disso)

USO:
  Ao rodar, o agente pergunta os dados do voo interativamente
  (numero do voo, origem, destino, aeronave, matricula, network).
  O token ACARS e lido de acars_token.txt na mesma pasta, ou
  pedido no primeiro uso e salvo para os proximos voos.

Ao encerrar (CTRL+C ou pos-parking), o agente envia:
  - PIREP resumo (endpoint /end)
  - Log completo (endpoint /log) com track e eventos
"""

import os
import sys
import time
import math
import json
from datetime import datetime, timezone

try:
    import requests
except ImportError:
    print("[ERRO] Modulo 'requests' nao instalado.")
    print("Abra o Prompt de Comando (cmd) e rode:  pip install requests fsuipc")
    input("\nPressione ENTER para sair...")
    sys.exit(1)

try:
    import pyuipc
except ImportError:
    print("[ERRO] Modulo 'pyuipc' nao instalado.")
    print("Abra o Prompt de Comando (cmd) e rode:  pip install fsuipc requests")
    print("(o pacote no PyPI se chama 'fsuipc' -- ele que fornece o modulo 'pyuipc' usado aqui.")
    print(" Instalar 'pyuipc' diretamente baixa um pacote errado, sem relacao com o simulador.)")
    input("\nPressione ENTER para sair...")
    sys.exit(1)

AGENT_VERSION = "2.0.0"
API_BASE = "https://tamvirtual.com.br/api/public/acars/live"

# Pasta onde salvar o token e os backups locais. Quando rodando como o .exe
# gerado pelo PyInstaller (--onefile), __file__ aponta pra uma pasta temporaria
# que e apagada ao fechar o programa -- por isso usamos a pasta do proprio
# executavel (sys.executable) nesse caso, em vez de __file__.
if getattr(sys, "frozen", False):
    BASE_DIR = os.path.dirname(sys.executable)
else:
    BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# =========================================================================
# FSUIPC OFFSETS
# Ref: http://www.fsuipc.com/  (FSUIPC Offset Reference)
# =========================================================================
OFFSETS = [
    (0x0560, "l"),   # 0  lat  (raw * 90 / (10001750*65536))
    (0x0568, "l"),   # 1  lon
    (0x0570, "l"),   # 2  alt m hi
    (0x0574, "l"),   # 3  alt m lo (fraction)
    (0x02B4, "d"),   # 4  ground speed (m/s * 65536)
    (0x02BC, "l"),   # 5  IAS (kt * 128)
    (0x02B8, "l"),   # 6  TAS (kt * 128)
    (0x11C6, "h"),   # 7  Mach (mach * 20480)
    (0x02CC, "l"),   # 8  heading (deg * 65536 * 65536 / 360)
    (0x02C8, "l"),   # 9  vertical speed (m/s * 256)
    (0x0578, "l"),   # 10 pitch  (raw * 360 / (65536*65536))  negativo = nariz cima
    (0x057C, "l"),   # 11 bank   (raw * 360 / (65536*65536))  negativo = direita
    (0x0366, "H"),   # 12 on ground (0/1)
    (0x0BC8, "H"),   # 13 parking brake (0=off, 32767=on)
    (0x0BE8, "L"),   # 14 gear position (0=up, 16383=down)
    (0x0BDC, "L"),   # 15 flaps handle (0..16383)
    (0x0BD0, "L"),   # 16 spoilers handle (0..16383)
    (0x07BC, "L"),   # 17 autopilot master (0/1)
    (0x0354, "H"),   # 18 transponder (BCD)
    (0x034E, "H"),   # 19 com1 freq (BCD)
    (0x0AF4, "H"),   # 20 engine 1 combustion (>0 running)
    (0x0B04, "H"),   # 21 engine 2 combustion
    (0x3BFC, "d"),   # 22 total fuel weight (kg via FSUIPC)  actually lbs*256 in some builds; treat conservatively
    (0x0E90, "H"),   # 23 wind speed kt
    (0x0E92, "H"),   # 24 wind direction (deg * 65536 / 360)
    (0x0E8A, "h"),   # 25 OAT (deg C * 256)
    (0x0264, "H"),   # 26 sim pause (0=running, 1=paused)
    (0x3D00, -256),  # 27 aircraft title (string 256)
]


def read_sim():
    r = pyuipc.read(OFFSETS)
    lat = r[0] * 90.0 / (10001750.0 * 65536.0)
    lon = r[1] * 180.0 / (10001750.0 * 65536.0)
    alt_m = r[2] + r[3] / 65536.0
    return {
        "lat": lat,
        "lon": lon,
        "altitude_ft": int(alt_m * 3.28084),
        "ground_speed_kt": int(r[4] / 65536.0 * 1.943844),
        "ias_kt": int(r[5] / 128.0),
        "tas_kt": int(r[6] / 128.0),
        "mach": round(r[7] / 20480.0, 3),
        "heading": int(r[8] * 360.0 / (65536.0 * 65536.0)) % 360,
        "vertical_speed_fpm": int(r[9] * 60.0 * 3.28084 / 256.0),
        "pitch": round(-r[10] * 360.0 / (65536.0 * 65536.0), 2),
        "bank": round(-r[11] * 360.0 / (65536.0 * 65536.0), 2),
        "on_ground": bool(r[12]),
        "parking_brake": r[13] > 0,
        "gear_pct": round(r[14] / 16383.0 * 100, 1),
        "flaps_pct": round(r[15] / 16383.0 * 100, 1),
        "spoilers_pct": round(r[16] / 16383.0 * 100, 1),
        "autopilot": bool(r[17]),
        "transponder": f"{r[18]:04X}",
        "com1_khz": 100000 + int(f"{r[19]:04X}") * 10,  # approx
        "eng1_running": r[20] > 0,
        "eng2_running": r[21] > 0,
        "fuel_kg": int(r[22]),
        "wind_kt": int(r[23]),
        "wind_dir": int(r[24] * 360.0 / 65536.0) % 360,
        "oat_c": int(r[25] / 256.0),
        "paused": bool(r[26]),
        "aircraft_title": r[27].decode("utf-8", errors="ignore").strip("\x00").strip() if isinstance(r[27], bytes) else str(r[27]),
    }


def haversine_nm(lat1, lon1, lat2, lon2):
    R = 3440.065  # nm
    p1, p2 = math.radians(lat1), math.radians(lat2)
    dp = math.radians(lat2 - lat1)
    dl = math.radians(lon2 - lon1)
    a = math.sin(dp/2)**2 + math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
    return 2 * R * math.asin(math.sqrt(a))


class FlightState:
    def __init__(self, cfg):
        self.cfg = cfg
        self.started_at = datetime.now(timezone.utc)
        self.t0 = time.time()
        self.track = []
        self.events = []
        self.was_airborne = False
        self.prev = None

        # metrics
        self.max_alt = 0
        self.max_gs = 0
        self.max_ias = 0
        self.max_mach = 0.0
        self.gs_sum = 0
        self.gs_count = 0
        self.distance_nm = 0.0
        self.fuel_start = None
        self.landing_rate = None
        self.g_force = None
        self.touchdown_lat = None
        self.touchdown_lon = None

        # phase flags for event detection
        self.flags = {
            "pushback": False, "taxi_out": False, "takeoff_roll": False,
            "rotate": False, "gear_up": False, "toc": False, "tod": False,
            "gear_down": False, "flaps_extended_final": False,
            "touchdown": False, "taxi_in": False, "shutdown": False,
        }
        self.top_alt_seen = 0

    def elapsed(self):
        return round(time.time() - self.t0, 1)

    def add_event(self, etype, data=None):
        self.events.append({"t": self.elapsed(), "type": etype, "data": data or {}})
        print(f"[EVENT +{int(self.elapsed())}s] {etype} {data or ''}")

    def update(self, s):
        # metrics
        if s["altitude_ft"] > self.max_alt: self.max_alt = s["altitude_ft"]
        if s["ground_speed_kt"] > self.max_gs: self.max_gs = s["ground_speed_kt"]
        if s["ias_kt"] > self.max_ias: self.max_ias = s["ias_kt"]
        if s["mach"] > self.max_mach: self.max_mach = s["mach"]
        self.gs_sum += s["ground_speed_kt"]; self.gs_count += 1
        if s["altitude_ft"] > self.top_alt_seen: self.top_alt_seen = s["altitude_ft"]
        if self.fuel_start is None and s["fuel_kg"] > 0:
            self.fuel_start = s["fuel_kg"]

        # distance
        if self.prev:
            d = haversine_nm(self.prev["lat"], self.prev["lon"], s["lat"], s["lon"])
            if 0 < d < 5:  # sanity filter
                self.distance_nm += d

        # phase detection
        phase = self.detect_phase(s)

        # event detection
        p = self.prev or s
        if not self.flags["pushback"] and s["ground_speed_kt"] < 3 and s["ground_speed_kt"] > 0 and s["parking_brake"] is False and (s["eng1_running"] or s["eng2_running"]):
            self.flags["pushback"] = True
            self.add_event("pushback_start")
        if not self.flags["taxi_out"] and s["ground_speed_kt"] >= 3 and s["on_ground"] and not self.was_airborne:
            self.flags["taxi_out"] = True
            self.add_event("taxi_out", {"fuel_kg": s["fuel_kg"]})
        if not self.flags["takeoff_roll"] and s["on_ground"] and s["ground_speed_kt"] > 40:
            self.flags["takeoff_roll"] = True
            self.add_event("takeoff_roll", {"ias": s["ias_kt"], "hdg": s["heading"]})
        if not self.flags["rotate"] and not s["on_ground"] and self.flags["takeoff_roll"]:
            self.flags["rotate"] = True
            self.add_event("rotate", {"ias": s["ias_kt"], "pitch": s["pitch"], "lat": s["lat"], "lon": s["lon"]})
            self.was_airborne = True
        if not self.flags["gear_up"] and self.was_airborne and s["gear_pct"] < 5:
            self.flags["gear_up"] = True
            self.add_event("gear_up", {"alt": s["altitude_ft"], "ias": s["ias_kt"]})
        if not self.flags["toc"] and self.was_airborne and s["vertical_speed_fpm"] < 200 and s["altitude_ft"] > 10000 and abs(s["altitude_ft"] - p["altitude_ft"]) < 100:
            self.flags["toc"] = True
            self.add_event("top_of_climb", {"alt": s["altitude_ft"], "mach": s["mach"]})
        if self.flags["toc"] and not self.flags["tod"] and s["vertical_speed_fpm"] < -300 and s["altitude_ft"] < self.top_alt_seen - 500:
            self.flags["tod"] = True
            self.add_event("top_of_descent", {"alt": s["altitude_ft"]})
        if not self.flags["gear_down"] and self.was_airborne and s["gear_pct"] > 95 and s["altitude_ft"] < 10000:
            self.flags["gear_down"] = True
            self.add_event("gear_down", {"alt": s["altitude_ft"], "ias": s["ias_kt"]})
        if not self.flags["flaps_extended_final"] and self.was_airborne and s["flaps_pct"] > 50 and s["altitude_ft"] < 5000:
            self.flags["flaps_extended_final"] = True
            self.add_event("flaps_full", {"alt": s["altitude_ft"], "ias": s["ias_kt"]})
        # touchdown
        if self.was_airborne and s["on_ground"] and not self.flags["touchdown"]:
            self.flags["touchdown"] = True
            self.landing_rate = p["vertical_speed_fpm"]
            dv = abs(s["vertical_speed_fpm"] - p["vertical_speed_fpm"]) / 60.0
            self.g_force = round(1.0 + dv / 32.174, 2)
            self.touchdown_lat = s["lat"]; self.touchdown_lon = s["lon"]
            self.add_event("touchdown", {
                "landing_rate_fpm": self.landing_rate, "g": self.g_force,
                "ias": s["ias_kt"], "gs": s["ground_speed_kt"],
                "pitch": s["pitch"], "bank": s["bank"],
                "wind_dir": s["wind_dir"], "wind_kt": s["wind_kt"],
                "lat": s["lat"], "lon": s["lon"],
            })
        if self.flags["touchdown"] and not self.flags["taxi_in"] and s["ground_speed_kt"] < 30 and s["ground_speed_kt"] > 3:
            self.flags["taxi_in"] = True
            self.add_event("taxi_in")
        if self.flags["taxi_in"] and not self.flags["shutdown"] and not s["eng1_running"] and not s["eng2_running"]:
            self.flags["shutdown"] = True
            self.add_event("engines_shutdown", {"fuel_kg": s["fuel_kg"]})

        # track point (compact)
        self.track.append({
            "t": self.elapsed(),
            "lat": round(s["lat"], 5),
            "lon": round(s["lon"], 5),
            "alt": s["altitude_ft"],
            "gs": s["ground_speed_kt"],
            "ias": s["ias_kt"],
            "hdg": s["heading"],
            "vs": s["vertical_speed_fpm"],
            "ptch": s["pitch"],
            "bnk": s["bank"],
            "fuel": s["fuel_kg"],
            "phase": phase,
        })

        self.prev = s
        return phase

    def detect_phase(self, s):
        if not self.was_airborne:
            if s["on_ground"] and s["ground_speed_kt"] < 3: return "boarding"
            if s["on_ground"] and s["ground_speed_kt"] < 40: return "taxi"
            if s["on_ground"] and s["ground_speed_kt"] >= 40: return "takeoff"
        else:
            if not s["on_ground"] and s["altitude_ft"] < 2000 and s["vertical_speed_fpm"] > 200: return "climb"
            if not s["on_ground"] and s["vertical_speed_fpm"] > 300: return "climb"
            if not s["on_ground"] and s["vertical_speed_fpm"] < -300: return "descent"
            if not s["on_ground"] and s["altitude_ft"] < 3000: return "approach"
            if s["on_ground"] and s["ground_speed_kt"] > 40: return "landing"
            if s["on_ground"] and s["ground_speed_kt"] > 3: return "taxi_in"
            if s["on_ground"] and s["ground_speed_kt"] <= 3: return "parked"
        return "cruise"

    def block_time_min(self):
        # first taxi_out or pushback to shutdown/taxi_in end
        t_start = next((e["t"] for e in self.events if e["type"] in ("pushback_start", "taxi_out")), 0)
        t_end = next((e["t"] for e in reversed(self.events) if e["type"] in ("engines_shutdown", "taxi_in", "touchdown")), self.elapsed())
        return max(0, int((t_end - t_start) / 60))

    def flight_time_min(self):
        t_r = next((e["t"] for e in self.events if e["type"] == "rotate"), None)
        t_t = next((e["t"] for e in self.events if e["type"] == "touchdown"), None)
        if t_r is not None and t_t is not None: return int((t_t - t_r) / 60)
        return None

    def build_log_payload(self, cfg):
        return {
            "flight_number": cfg["flight_number"],
            "dep_icao": cfg["dep_icao"],
            "arr_icao": cfg["arr_icao"],
            "aircraft_type": cfg.get("aircraft_type"),
            "aircraft_reg": cfg.get("aircraft_reg"),
            "aircraft_title": self.prev.get("aircraft_title") if self.prev else None,
            "network": cfg.get("network"),
            "started_at": self.started_at.isoformat(),
            "ended_at": datetime.now(timezone.utc).isoformat(),
            "block_time_min": self.block_time_min(),
            "flight_time_min": self.flight_time_min(),
            "distance_nm": round(self.distance_nm, 2),
            "max_altitude_ft": self.max_alt,
            "max_ground_speed_kt": self.max_gs,
            "max_ias_kt": self.max_ias,
            "max_mach": self.max_mach,
            "avg_ground_speed_kt": int(self.gs_sum / max(1, self.gs_count)),
            "fuel_used_kg": max(0, (self.fuel_start or 0) - (self.prev["fuel_kg"] if self.prev else 0)),
            "landing_rate_fpm": self.landing_rate,
            "g_force": self.g_force,
            "touchdown_lat": self.touchdown_lat,
            "touchdown_lon": self.touchdown_lon,
            "agent_version": AGENT_VERSION,
            "track": self.track[-20000:],  # cap for safety
            "events": self.events,
            "summary": {
                "flags": self.flags,
                "fuel_start_kg": self.fuel_start,
                "fuel_end_kg": self.prev["fuel_kg"] if self.prev else None,
            },
        }


# =========================================================================
# TOKEN + FLIGHT CONFIG (interactive)
# =========================================================================
def load_token():
    path = os.path.join(BASE_DIR, "acars_token.txt")
    if os.path.exists(path):
        with open(path, "r", encoding="utf-8") as f:
            t = f.read().strip()
            if t: return t, path
    print("Pegue seu token em: https://tamvirtual.com.br/voar")
    t = input("Cole seu ACARS Token: ").strip()
    if t:
        with open(path, "w", encoding="utf-8") as f: f.write(t)
        print(f"[OK] Token salvo em {path}")
    return t, path


def ask_flight():
    print("\n=== Dados do Voo ===")
    flight_number = input("Numero do voo (ex: TAM3001): ").strip().upper() or "TAM3001"
    dep_icao = input("Origem ICAO (ex: SBGR): ").strip().upper() or "SBGR"
    arr_icao = input("Destino ICAO (ex: SBRJ): ").strip().upper() or "SBRJ"
    aircraft_type = input("Tipo aeronave (ex: A320) [opcional]: ").strip().upper() or None
    aircraft_reg = input("Matricula (ex: PR-MYA) [opcional]: ").strip().upper() or None
    network = (input("Network (IVAO/VATSIM/OFFLINE) [OFFLINE]: ").strip().upper() or "OFFLINE")
    return {
        "flight_number": flight_number, "dep_icao": dep_icao, "arr_icao": arr_icao,
        "aircraft_type": aircraft_type, "aircraft_reg": aircraft_reg, "network": network,
    }


# =========================================================================
# HTTP
# =========================================================================
def http_post(token, path, payload, timeout=15):
    url = f"{API_BASE}/{path}"
    try:
        r = requests.post(url, json=payload, headers={
            "X-Acars-Token": token,
            "User-Agent": f"TAM-ACARS-Agent/{AGENT_VERSION}",
        }, timeout=timeout)
        if r.status_code >= 400:
            print(f"[HTTP] {path} -> {r.status_code} {r.text[:200]}")
            return False
        return True
    except Exception as e:
        print(f"[HTTP] {path} erro: {e}")
        return False


# =========================================================================
# MAIN
# =========================================================================
def main():
    print(f"=== TAM Virtual ACARS Agent v{AGENT_VERSION} ===\n")
    token, _ = load_token()
    if not token:
        print("Sem token. Encerrando.")
        sys.exit(1)

    cfg = ask_flight()

    print("\n[SIM] Conectando ao FSUIPC...")
    try:
        pyuipc.open(0)
    except Exception as e:
        print(f"[SIM] Falha: {e}")
        print("Abra o simulador com FSUIPC7 (MSFS) ou FSUIPC5/6 (P3D/FSX) primeiro.")
        input("Pressione ENTER para sair...")
        sys.exit(1)
    print("[SIM] Conectado!\n")

    fs = FlightState(cfg)
    s0 = read_sim()
    fs.prev = s0
    fs.fuel_start = s0["fuel_kg"]

    # send flight start
    print(f"[TAM] Iniciando {cfg['flight_number']}  {cfg['dep_icao']} -> {cfg['arr_icao']}")
    if not http_post(token, "start", {
        **cfg, "fuel_kg": s0["fuel_kg"], "lat": s0["lat"], "lon": s0["lon"],
    }):
        print("[TAM] Falha ao iniciar (token invalido?). Encerrando.")
        pyuipc.close()
        sys.exit(1)

    print("[TAM] Enviando dados a cada 3s. CTRL+C para encerrar e enviar o log completo.\n")

    UPDATE_INTERVAL = 3
    try:
        while True:
            s = read_sim()
            if s.get("paused"):
                time.sleep(1); continue
            phase = fs.update(s)
            http_post(token, "update", {
                "lat": s["lat"], "lon": s["lon"],
                "altitude_ft": s["altitude_ft"], "ground_speed_kt": s["ground_speed_kt"],
                "heading": s["heading"], "vertical_speed_fpm": s["vertical_speed_fpm"],
                "on_ground": s["on_ground"], "fuel_kg": s["fuel_kg"],
                "wind_dir": s["wind_dir"], "wind_kt": s["wind_kt"], "oat_c": s["oat_c"],
                "phase": phase,
            })
            time.sleep(UPDATE_INTERVAL)

    except KeyboardInterrupt:
        print("\n[TAM] Encerrando voo e enviando log completo...")

    # Final flush: end + full log
    http_post(token, "end", {
        "landing_rate_fpm": fs.landing_rate,
        "g_force": fs.g_force,
        "crashed": False,
    })

    payload = fs.build_log_payload(cfg)
    print(f"[TAM] Log: {len(payload['track'])} pontos, {len(payload['events'])} eventos, "
          f"{payload['distance_nm']} nm, TT {payload['flight_time_min']}min, "
          f"pouso {payload['landing_rate_fpm']} fpm")

    # local backup
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    local_path = os.path.join(BASE_DIR, f"log_{cfg['flight_number']}_{ts}.json")
    try:
        with open(local_path, "w", encoding="utf-8") as f: json.dump(payload, f)
        print(f"[TAM] Backup salvo em: {local_path}")
    except Exception as e:
        print(f"[TAM] Backup local falhou: {e}")

    ok = http_post(token, "log", payload, timeout=60)
    if ok:
        print("[TAM] Log completo enviado. PIREP em processamento. Bom voo!")
    else:
        print(f"[TAM] Falha ao enviar o log. Use o backup local: {local_path}")

    try: pyuipc.close()
    except Exception: pass


if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"[FATAL] {e}")
    input("\nPressione ENTER para sair...")
