#!/usr/bin/env python3
"""
ADN UDP Logger — pistar.com.tr
Kurulum gerekmez, Python 3.6+ yeterli.

Kullanım:
  python3 udp_logger.py --callsign TA2BBE --token TOKEN_BURAYA

Token: pistar.com.tr logbook'a giriş yapın,
tarayıcı DevTools > Application > LocalStorage > adn-qsl-token
"""

import socket, json, struct, xml.etree.ElementTree as ET
import argparse, threading, time, sys
from datetime import datetime, timezone
from urllib import request as urlreq, parse as urlparse, error as urlerr

# ── Config ────────────────────────────────────────────────────────────────────
API_URL = "https://pistar.com.tr/qsl_api.php"
PORTS   = {"n1mm": 12060, "wsjtx": 2237, "log4om": 2238}
BUFF    = 65535

parser = argparse.ArgumentParser(description="ADN UDP Logger")
parser.add_argument("--callsign", required=True)
parser.add_argument("--token",    required=True)
parser.add_argument("--host",     default="0.0.0.0")
parser.add_argument("--port",     default=None, type=int)
parser.add_argument("--server",   default=API_URL)
args = parser.parse_args()

last_sent = {}  # rate limiting

# ── HTTP (urllib, no deps) ─────────────────────────────────────────────────────
def api(action, data):
    try:
        url  = f"{args.server}?action={action}&token={urlparse.quote(args.token)}"
        body = json.dumps(data).encode("utf-8")
        req  = urlreq.Request(url, data=body, method="POST")
        req.add_header("Content-Type", "application/json")
        req.add_header("Authorization", f"Bearer {args.token}")
        with urlreq.urlopen(req, timeout=6) as resp:
            result = json.loads(resp.read())
            if result.get("ok"):
                print(f"  ✅ {action}: {data.get('callsign','?')} "
                      f"{data.get('freq','?')} {data.get('mode','?')}")
            else:
                print(f"  ⚠️  {result.get('msg','?')}")
    except urlerr.URLError as e:
        print(f"  ❌ Bağlantı hatası: {e}")
    except Exception as e:
        print(f"  ❌ Hata: {e}")

def activity(freq, mode, note=""):
    key = f"{round(float(freq),3)}_{mode}"
    now = time.time()
    if now - last_sent.get(key, 0) < 60:
        return
    last_sent[key] = now
    api("setactivity", {"callsign": args.callsign,
                        "freq": freq, "mode": mode, "note": note})

def log_qso(call, freq, mode, date, tme,
            rst_s="59", rst_r="59", dxcc="", grid="", note=""):
    api("addqso", {"callsign": call.upper(), "freq": freq,
                   "mode": mode.upper(), "date": date, "time": tme,
                   "rst_s": rst_s, "rst_r": rst_r,
                   "dxcc": dxcc, "grid": grid, "note": note})

# ── N1MM+ ─────────────────────────────────────────────────────────────────────
def parse_n1mm(data):
    try:
        root = ET.fromstring(data.decode("utf-8", errors="ignore"))
        tag  = root.tag.lower()
        if tag == "contactinfo":
            call  = root.findtext("call","").strip()
            freq  = float(root.findtext("rxfreq","0")) / 100  # Hz/100 → MHz
            mode  = root.findtext("mode","SSB").strip()
            rst_s = root.findtext("snt","59").strip()
            rst_r = root.findtext("rcv","59").strip()
            dxcc  = root.findtext("country","").strip()
            grid  = root.findtext("gridsquare","").strip()
            ts    = root.findtext("timestamp","")
            if call and freq > 0:
                try:
                    dt     = datetime.strptime(ts, "%Y-%m-%d %H:%M:%S")
                    date_s = dt.strftime("%Y-%m-%d")
                    time_s = dt.strftime("%H:%M")
                except Exception:
                    now    = datetime.now(timezone.utc)
                    date_s = now.strftime("%Y-%m-%d")
                    time_s = now.strftime("%H:%M")
                print(f"  📋 N1MM QSO: {call} {freq:.3f} MHz {mode}")
                log_qso(call, freq, mode, date_s, time_s, rst_s, rst_r, dxcc, grid)
        elif tag == "radioinfo":
            freq = float(root.findtext("freq","0")) / 1000000
            mode = root.findtext("mode","SSB").strip()
            if freq > 0:
                activity(round(freq,4), mode, "N1MM+")
    except ET.ParseError:
        pass
    except Exception as e:
        print(f"  N1MM hata: {e}")

# ── WSJT-X ────────────────────────────────────────────────────────────────────
WSJTX_MAGIC  = 0xadbccbda
WSJTX_STATUS = 1
WSJTX_LOGGED = 5

def read_str(buf, p):
    if p + 4 > len(buf):
        return "", p
    l = struct.unpack(">I", buf[p:p+4])[0]
    if l == 0xFFFFFFFF:
        return "", p + 4
    s = buf[p+4:p+4+l].decode("utf-8", "ignore")
    return s, p + 4 + l

def parse_wsjtx(data):
    if len(data) < 12:
        return
    try:
        magic = struct.unpack(">I", data[0:4])[0]
        if magic != WSJTX_MAGIC:
            return
        msgtype = struct.unpack(">I", data[8:12])[0]
        pos = 12
        _, pos = read_str(data, pos)   # skip ID

        if msgtype == WSJTX_STATUS:
            if pos + 8 > len(data): return
            freq = struct.unpack(">Q", data[pos:pos+8])[0] / 1e6
            pos += 8
            mode, pos = read_str(data, pos)
            _, pos    = read_str(data, pos)   # DX call
            _, pos    = read_str(data, pos)   # report
            if freq > 0:
                activity(round(freq, 4), mode or "FT8", "WSJT-X")

        elif msgtype == WSJTX_LOGGED:
            pos += 8  # skip timestamp
            dx_call, pos = read_str(data, pos)
            dx_grid, pos = read_str(data, pos)
            freq = struct.unpack(">Q", data[pos:pos+8])[0]/1e6 if pos+8<=len(data) else 0
            pos += 8
            mode,  pos = read_str(data, pos)
            rst_s, pos = read_str(data, pos)
            rst_r, pos = read_str(data, pos)
            if dx_call:
                now = datetime.now(timezone.utc)
                print(f"  📋 WSJT-X QSO: {dx_call} {freq:.3f} MHz {mode}")
                log_qso(dx_call, round(freq,4), mode or "FT8",
                        now.strftime("%Y-%m-%d"), now.strftime("%H:%M"),
                        rst_s, rst_r, "", dx_grid)
    except Exception as e:
        print(f"  WSJT-X hata: {e}")

# ── Log4OM ────────────────────────────────────────────────────────────────────
def parse_log4om(data):
    try:
        obj  = json.loads(data.decode("utf-8","ignore"))
        t    = obj.get("type","").lower()
        call = obj.get("callsign") or obj.get("call","")
        freq = float(obj.get("frequency") or obj.get("freq", 0))
        mode = obj.get("mode","SSB")
        if t in ("qso","contact") and call and freq > 0:
            date = obj.get("date","") or datetime.now().strftime("%Y-%m-%d")
            tme  = obj.get("time","") or datetime.now().strftime("%H:%M")
            print(f"  📋 Log4OM QSO: {call} {freq:.3f} MHz {mode}")
            log_qso(call, freq, mode, date, tme,
                    obj.get("rst_sent","59"), obj.get("rst_rcvd","59"),
                    obj.get("country",""), obj.get("grid",""))
        elif t in ("radio","frequency") and freq > 0:
            activity(freq, mode, "Log4OM")
    except Exception as e:
        print(f"  Log4OM hata: {e}")

# ── UDP Listener ──────────────────────────────────────────────────────────────
def listen(port, parser_fn, name):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.settimeout(1.0)
    try:
        sock.bind((args.host, port))
        print(f"  👂 {name:<10} dinleniyor → {args.host}:{port}")
    except OSError as e:
        print(f"  ⚠️  Port {port} açılamadı → {name} devre dışı ({e})")
        return
    while True:
        try:
            data, _ = sock.recvfrom(BUFF)
            parser_fn(data)
        except socket.timeout:
            continue
        except Exception as e:
            print(f"  Socket hata {name}: {e}")
            break

# ── Systemd servis dosyası yaz ───────────────────────────────────────────────
def write_service():
    svc = f"""[Unit]
Description=ADN UDP Logger
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/python3 /var/www/html/udp_logger.py --callsign {args.callsign} --token {args.token}
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
"""
    path = "/tmp/adn-udp-logger.service"
    with open(path, "w") as f:
        f.write(svc)
    print(f"\n  Servis dosyası oluşturuldu: {path}")
    print("  Aktif etmek için:")
    print(f"    sudo cp {path} /etc/systemd/system/")
    print("    sudo systemctl daemon-reload")
    print("    sudo systemctl enable --now adn-udp-logger")

# ── Main ──────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    print(f"""
╔══════════════════════════════════════════════════════╗
║          ADN UDP Logger — pistar.com.tr              ║
╠══════════════════════════════════════════════════════╣
║  Çağrı  : {args.callsign:<43}║
║  Sunucu : {args.server:<43}║
╚══════════════════════════════════════════════════════╝""")

    # API bağlantı testi
    try:
        req = urlreq.Request(f"{args.server}?action=ping", method="GET")
        with urlreq.urlopen(req, timeout=5) as resp:
            result = json.loads(resp.read())
            if result.get("ok"):
                print("\n  ✅ API bağlantısı OK")
            else:
                print(f"\n  ⚠️  API: {result}")
    except Exception as e:
        print(f"\n  ❌ API bağlantısı başarısız: {e}")
        print("     Devam ediliyor...\n")

    port_map = [
        (PORTS["n1mm"],   parse_n1mm,   "N1MM+"),
        (PORTS["wsjtx"],  parse_wsjtx,  "WSJT-X"),
        (PORTS["log4om"], parse_log4om, "Log4OM"),
    ]
    if args.port:
        port_map = [(args.port, parse_n1mm, "Custom")]

    print()
    for port, fn, name in port_map:
        t = threading.Thread(target=listen, args=(port, fn, name), daemon=True)
        t.start()

    write_service()

    print("\n  Çalışıyor — durdurmak için Ctrl+C\n")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print("\n  👋 Kapatılıyor...")
        sys.exit(0)
