#!/usr/bin/env python3
"""
ha-nut-setup.py — Add the Home Assistant NUT integration via REST config-flow API.

Stdlib-only (no pip). Idempotent: skips if a NUT config entry already exists.

Env:
  HA_HOST   (default pfv-bms.knel.net)
  HA_PORT   (default 8123)
  HA_TOKEN  (long-lived access token)
  NUT_HOST  (default 100.121.189.98)
  NUT_PORT  (default 3493)
  NUT_USER  (default homeassistant)
  NUT_PASS  (required)
  NUT_UPS   (default apc-smartups-c1500)
"""
import os, json, sys, time, urllib.request, urllib.error

HA_HOST  = os.environ.get("HA_HOST", "pfv-bms.knel.net")
HA_PORT  = int(os.environ.get("HA_PORT", "8123"))
TOKEN    = os.environ["HA_TOKEN"]
NUT_HOST = os.environ.get("NUT_HOST", "192.168.3.11")
NUT_PORT = int(os.environ.get("NUT_PORT", "3493"))
NUT_USER = os.environ.get("NUT_USER", "homeassistant")
NUT_PASS = os.environ["NUT_PASS"]
NUT_UPS  = os.environ.get("NUT_UPS", "apc-smartups-c1500")
BASE     = f"http://{HA_HOST}:{HA_PORT}"

def api(method, path, data=None):
    body = json.dumps(data).encode() if data else None
    req = urllib.request.Request(
        f"{BASE}/api{path}", data=body, method=method,
        headers={"Authorization": f"Bearer {TOKEN}",
                 "Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=20) as r:
            return json.loads(r.read())
    except urllib.error.HTTPError as e:
        raw = e.read().decode()
        try:
            return json.loads(raw)
        except Exception:
            return {"_http_error": e.code, "_raw": raw[:300]}
    except Exception as e:
        return {"_error": str(e)}

# ── verify token ──
cfg = api("GET", "/config")
if "_http_error" in cfg or "_error" in cfg:
    print(f"Cannot reach HA or token invalid: {cfg}"); sys.exit(1)
print(f"HA {cfg.get('version')} — token valid")

# ── check existing entries (idempotent) ──
entries = api("GET", "/config/config_entries/entry")
existing = [e for e in entries if e.get("domain") == "nut"]
if existing:
    for e in existing:
        print(f"NUT already configured: {e.get('title')} "
              f"(data={json.dumps(e.get('data', {}))})")
    print("Skipping — delete it in HA UI first if you want to re-run.")
    sys.exit(0)
print("No existing NUT entry. Starting config flow.")

# ── initiate flow ──
flow = api("POST", "/config/config_entries/flow", {"handler": "nut"})
if "flow_id" not in flow:
    print(f"Flow init failed: {json.dumps(flow)}"); sys.exit(1)
fid = flow["flow_id"]
print(f"Flow started: step={flow.get('step_id')} "
      f"fields={[f.get('name') for f in flow.get('data_schema', [])]}")

# ── submit connection details ──
creds = {"host": NUT_HOST, "port": NUT_PORT,
         "username": NUT_USER, "password": NUT_PASS}
flow = api("POST", f"/config/config_entries/flow/{fid}", creds)
if flow.get("errors"):
    print(f"Validation errors: {flow['errors']}"); sys.exit(1)
print(f"After submit: type={flow.get('type')} step={flow.get('step_id')}")

# ── handle follow-up steps (UPS selection etc.) ──
while flow.get("type") == "form":
    step = flow.get("step_id", "?")
    schema = flow.get("data_schema", [])
    print(f"Step '{step}': fields={[f.get('name') for f in schema]}")
    for f in schema:
        opts = f.get("options") or f.get("values")
        if opts:
            print(f"  {f.get('name')} options: {opts}")
    submission = {}
    for f in schema:
        nm = f.get("name")
        ftype = f.get("type", "")
        if ftype == "multi_select":
            opts = f.get("options", [])
            vals = [o[0] if isinstance(o, list) else o for o in opts]
            submission[nm] = [NUT_UPS] if NUT_UPS in vals else vals[:1]
        elif nm in creds:
            submission[nm] = creds[nm]
        elif "default" in f:
            submission[nm] = f["default"]
        elif ftype == "select":
            opts = f.get("options", [])
            vals = [o[0] if isinstance(o, list) else o for o in opts]
            submission[nm] = NUT_UPS if NUT_UPS in vals else (vals[0] if vals else "")
    fid = flow.get("flow_id", fid)
    flow = api("POST", f"/config/config_entries/flow/{fid}", submission)
    if flow.get("errors"):
        print(f"Validation errors: {flow['errors']}"); sys.exit(1)
    print(f"  -> type={flow.get('type')} step={flow.get('step_id')}")

# ── result ──
if flow.get("type") == "create_entry":
    print(f"\nNUT integration created: {flow.get('title')}")
elif flow.get("type") == "abort":
    print(f"\nFlow aborted: {flow.get('reason')}"); sys.exit(1)
else:
    print(f"\nFinal state: {flow.get('type')} — {json.dumps(flow)[:200]}")

# ── verify sensors ──
print("\nWaiting 10s for entities ...")
time.sleep(10)
states = api("GET", "/states")
ups = [s for s in states
       if "apc_smartups" in s["entity_id"].lower()
       or "sensor.ups_" in s["entity_id"].lower()]
if ups:
    print(f"Found {len(ups)} UPS sensors:")
    for e in sorted(ups, key=lambda x: x["entity_id"]):
        st = e.get("state", "?")
        unit = e.get("attributes", {}).get("unit_of_measurement", "")
        name = e.get("attributes", {}).get("friendly_name", "")
        print(f"  {e['entity_id']:55s} {st:>8} {unit:4s} {name}")
else:
    print("No UPS sensors yet (may still be initialising — check HA UI).")
