#!/usr/bin/env python3
"""
NW AL Scanner Baron Lightning Bridge v001
-----------------------------------------
Loads the organization's authorized Baron widget in installed Google Chrome,
captures live Alabama lightning responses, and writes a normalized local file:

    baron_lightning.json

Output format matches the existing V3 broadcast lightning consumer:
    [{"lat": 34.8, "lon": -87.7, "t": 1784235000000}, ...]

Requirements:
    py -m pip install playwright

This script uses the already-installed Google Chrome channel, so a separate
Playwright Chromium download is not required.
"""

import asyncio
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path

from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeoutError

BASE_DIR = Path(__file__).resolve().parent
OUTPUT_FILE = BASE_DIR / "baron_lightning.json"
STATUS_FILE = BASE_DIR / "baron_lightning_status.json"

WIDGET_URL = (
    "https://staticbaronwebapps.velocityweather.com/"
    "digitial_wx/widgets/mapv2/index.html"
    "?initjson=/digitial_wx/widgets/dcms/"
    "017da136-5149-4082-aed2-93cd4f1a1683/live/init.json"
    "#8.58/34.774/-87.6187"
)

# Alabama/Tennessee Valley region observed from the licensed widget.
TARGET_NORTH = 36.0
TARGET_SOUTH = 33.0
TARGET_WEST = -88.0
TARGET_EAST = -84.0

RELOAD_EVERY_SECONDS = 25 * 60
STALE_AFTER_SECONDS = 5 * 60
PAGE_SIZE_LIMIT = 1000

def utc_now_iso():
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")

def write_json_atomic(path: Path, payload):
    temp = path.with_suffix(path.suffix + ".tmp")
    temp.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    temp.replace(path)

def write_status(state, **extra):
    payload = {
        "provider": "baron",
        "state": state,
        "updated": utc_now_iso(),
        **extra,
    }
    write_json_atomic(STATUS_FILE, payload)

def normalize_payload(payload):
    if not isinstance(payload, list):
        return []

    now_ms = int(time.time() * 1000)
    seen = set()
    output = []

    for item in payload:
        if not isinstance(item, dict):
            continue

        coords = item.get("coordinates") or item.get("coord") or item.get("location")
        if not isinstance(coords, (list, tuple)) or len(coords) < 2:
            continue

        try:
            lon = float(coords[0])
            lat = float(coords[1])
        except (TypeError, ValueError):
            continue

        raw_time = item.get("time") or item.get("timestamp") or item.get("t")
        ts_ms = None

        if isinstance(raw_time, (int, float)):
            ts_ms = int(raw_time)
            if ts_ms < 1_000_000_000_000:
                ts_ms *= 1000
        elif isinstance(raw_time, str):
            try:
                ts_ms = int(
                    datetime.fromisoformat(raw_time.replace("Z", "+00:00")).timestamp() * 1000
                )
            except ValueError:
                continue

        if ts_ms is None:
            continue

        # Keep a generous 20-minute local window. V3 itself trims to 15 minutes.
        if now_ms - ts_ms > 20 * 60 * 1000:
            continue
        if ts_ms - now_ms > 5 * 60 * 1000:
            continue

        key = (round(lat, 4), round(lon, 4), round(ts_ms / 1000))
        if key in seen:
            continue
        seen.add(key)

        output.append({
            "lat": lat,
            "lon": lon,
            "t": ts_ms,
            "type": item.get("type", ""),
            "intensity": item.get("intensity"),
            "multiplicity": item.get("multiplicity"),
        })

    output.sort(key=lambda strike: strike["t"])
    return output

def is_target_region(url: str):
    return (
        "/reports/lightning/region.json" in url
        and f"n_lat={int(TARGET_NORTH)}" in url
        and f"s_lat={int(TARGET_SOUTH)}" in url
        and f"w_lon={int(TARGET_WEST)}" in url
        and f"e_lon={int(TARGET_EAST)}" in url
    )

async def enable_lightning(page):
    """
    Attempts several UI strategies because Baron may alter markup while keeping
    the visible product name unchanged.
    """
    candidates = [
        page.get_by_text("Lightning", exact=True),
        page.get_by_text("LIGHTNING", exact=True),
        page.locator("text=Lightning"),
        page.locator('[title*="Lightning" i]'),
        page.locator('[aria-label*="Lightning" i]'),
    ]

    for locator in candidates:
        try:
            if await locator.count():
                await locator.first.click(timeout=3000, force=True)
                print("[BARON] Lightning layer clicked.", flush=True)
                return True
        except Exception:
            pass

    # Open likely product/layer menus, then try once more.
    menu_candidates = [
        page.locator('[aria-label*="layer" i]'),
        page.locator('[title*="layer" i]'),
        page.locator('button:has-text("Products")'),
        page.locator('button:has-text("Layers")'),
    ]

    for locator in menu_candidates:
        try:
            if await locator.count():
                await locator.first.click(timeout=2500, force=True)
                await page.wait_for_timeout(800)
                lightning = page.get_by_text("Lightning", exact=True)
                if await lightning.count():
                    await lightning.first.click(timeout=3000, force=True)
                    print("[BARON] Lightning layer enabled from menu.", flush=True)
                    return True
        except Exception:
            pass

    print("[BARON] Could not positively click Lightning; waiting for widget requests.", flush=True)
    return False

async def run_bridge():
    last_capture = 0.0
    latest_count = 0
    capture_event = asyncio.Event()

    async with async_playwright() as p:
        browser = await p.chromium.launch(
            channel="chrome",
            headless=True,
            args=[
                "--disable-gpu",
                "--disable-dev-shm-usage",
                "--no-first-run",
                "--no-default-browser-check",
            ],
        )

        context = await browser.new_context(
            viewport={"width": 1440, "height": 900},
            locale="en-US",
        )
        page = await context.new_page()

        async def handle_response(response):
            nonlocal last_capture, latest_count

            if not is_target_region(response.url):
                return

            try:
                payload = await response.json()
                strikes = normalize_payload(payload)
                write_json_atomic(OUTPUT_FILE, strikes)

                last_capture = time.time()
                latest_count = len(strikes)
                write_status(
                    "live",
                    strike_count=latest_count,
                    source_url=response.url.split("&sig=")[0] + "&sig=REDACTED",
                    age_seconds=0,
                )

                print(
                    f"[BARON] Captured Alabama lightning: {latest_count} recent strikes",
                    flush=True,
                )
                capture_event.set()
            except Exception as exc:
                print(f"[BARON] Response parse failed: {exc}", flush=True)
                write_status("error", error=str(exc))

        page.on("response", handle_response)

        while True:
            try:
                capture_event.clear()
                print("[BARON] Loading authorized widget...", flush=True)
                write_status("loading", strike_count=latest_count)

                await page.goto(WIDGET_URL, wait_until="domcontentloaded", timeout=45_000)
                await page.wait_for_timeout(5000)
                await enable_lightning(page)

                try:
                    await asyncio.wait_for(capture_event.wait(), timeout=45)
                except asyncio.TimeoutError:
                    print("[BARON] No Alabama lightning response captured yet.", flush=True)

                cycle_started = time.time()
                while time.time() - cycle_started < RELOAD_EVERY_SECONDS:
                    age = time.time() - last_capture if last_capture else None
                    if age is not None and age > STALE_AFTER_SECONDS:
                        write_status(
                            "stale",
                            strike_count=latest_count,
                            age_seconds=int(age),
                        )

                    await page.wait_for_timeout(15_000)

                print("[BARON] Refreshing widget for a new authorization window.", flush=True)

            except PlaywrightTimeoutError as exc:
                print(f"[BARON] Widget timeout: {exc}", flush=True)
                write_status("error", error=f"Widget timeout: {exc}")
                await page.wait_for_timeout(15_000)
            except Exception as exc:
                print(f"[BARON] Bridge error: {exc}", flush=True)
                write_status("error", error=str(exc))
                await page.wait_for_timeout(15_000)

async def main():
    if not OUTPUT_FILE.exists():
        write_json_atomic(OUTPUT_FILE, [])
    write_status("starting", strike_count=0)
    await run_bridge()

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        write_status("stopped")
        print("\n[BARON] Bridge stopped.", flush=True)
