#!/usr/bin/env python3
"""SKY-Cockpit DWD-Poller (warnings.json + KONRAD-WFS Server-Cache).

Session 92 (29.05.2026): Phase 1 von 'Cache-everything'-Architektur.
Holt alle 30s vom DWD und legt JSON unter /opt/mapproxy/static/dwd/ ab.
Cockpit fetcht von dort statt direkt von dwd.de — bei DWD-Crash bleibt
der alte Stand erreichbar, Browser-Start in ~2s statt 30s.

Idempotenz: schreibt nur wenn timestamp im Response neuer ist als das,
was schon im File steht. Atomic write via tmp + rename.
"""
import datetime, json, re, sys, time
from pathlib import Path
import urllib.request

OUT_DIR = Path('/opt/mapproxy/static/dwd')
OUT_DIR.mkdir(parents=True, exist_ok=True)

DWD_WARNINGS_URL = 'https://www.dwd.de/DWD/warnungen/warnapp/json/warnings.json'
KONRAD_WFS_URL   = ('https://maps.dwd.de/geoserver/dwd/ows?service=WFS&version=2.0.0'
                   '&request=GetFeature&typeNames=dwd:Gewitterzellen&outputFormat=application/json')
TIMEOUT = 20


def fetch_url(url):
    req = urllib.request.Request(url, headers={'User-Agent':'SKY-DWD-Poll/1.0'})
    with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
        if r.status != 200:
            raise Exception('HTTP ' + str(r.status))
        return r.read()


def atomic_write(path, data):
    tmp = path.with_suffix(path.suffix + '.tmp')
    tmp.write_bytes(data)
    tmp.rename(path)


def poll_warnings():
    """Holt warnings.json (JSONP-Wrap), schreibt nach warnings.json + status."""
    started = time.time()
    name = 'warnings'
    try:
        data = fetch_url(DWD_WARNINGS_URL)
        raw  = data.decode('utf-8', errors='ignore')
        m    = re.search(r'warnWetter\.loadWarnings\((.*)\);?', raw, re.DOTALL)
        body = m.group(1) if m else raw
        j    = json.loads(body)
        new_ts = j.get('time')
    except Exception as e:
        print('  ' + name + ': FAIL ' + type(e).__name__ + ': ' + str(e), flush=True)
        return False
    out_path = OUT_DIR / 'warnings.json'
    try:
        old_ts = json.loads(out_path.read_text()).get('time')
    except Exception:
        old_ts = None
    if old_ts and old_ts == new_ts:
        print('  ' + name + ': same ts ' + str(new_ts) + ' (' + str(round(time.time()-started,2)) + 's)', flush=True)
        return True
    atomic_write(out_path, json.dumps(j, separators=(',',':')).encode('utf-8'))
    print('  ' + name + ': updated ' + str(new_ts) + ' (' + str(round(time.time()-started,2)) + 's, ' + str(len(data)) + ' raw bytes)', flush=True)
    return True


def poll_konrad():
    """Holt KONRAD-WFS GeoJSON, schreibt nach konrad.json."""
    started = time.time()
    name = 'konrad'
    try:
        data = fetch_url(KONRAD_WFS_URL)
        j    = json.loads(data.decode('utf-8'))
        new_ts = j.get('timeStamp')
    except Exception as e:
        print('  ' + name + ': FAIL ' + type(e).__name__ + ': ' + str(e), flush=True)
        return False
    out_path = OUT_DIR / 'konrad.json'
    try:
        old_ts = json.loads(out_path.read_text()).get('timeStamp')
    except Exception:
        old_ts = None
    if old_ts and old_ts == new_ts:
        print('  ' + name + ': same ts ' + str(new_ts) + ' (' + str(round(time.time()-started,2)) + 's)', flush=True)
        return True
    atomic_write(out_path, json.dumps(j, separators=(',',':')).encode('utf-8'))
    print('  ' + name + ': updated ' + str(new_ts) + ' (' + str(round(time.time()-started,2)) + 's, ' + str(len(data)) + ' raw bytes, ' + str(j.get('totalFeatures','?')) + ' features)', flush=True)
    return True


def main():
    started = time.time()
    print('[' + datetime.datetime.utcnow().isoformat(timespec='seconds') + 'Z] dwd-poll start', flush=True)
    ok = 0
    if poll_warnings(): ok += 1
    if poll_konrad():   ok += 1
    elapsed = round(time.time() - started, 2)
    print('  total: ' + str(ok) + '/2 OK, ' + str(elapsed) + 's', flush=True)
    sys.exit(0 if ok == 2 else 1)


if __name__ == '__main__':
    main()
