"""Read WLED capabilities/state without changing the controller. Python 3, no packages.

Usage: python3 wled-inspect.py --host wled-kitchen-1.local --host wled-kitchen-2.local
Offline: python3 wled-inspect.py --file saved-json-response.json
Only explicit hosts are contacted (GET /json); no discovery, POSTs or telemetry.
Output omits hostnames, IPs, MACs and device names. Inspect any output before sharing.
"""
import argparse
import json
import sys
import urllib.error
import urllib.parse
import urllib.request


def capabilities(value):
    if not isinstance(value, int) or isinstance(value, bool) or value < 0:
        return None
    return {"rgb": bool(value & 1), "white": bool(value & 2), "cct": bool(value & 4)}


def summarize(data):
    if not isinstance(data, dict):
        raise ValueError("Expected the object returned by /json")
    info, state = data.get("info"), data.get("state")
    if not isinstance(info, dict) or not isinstance(state, dict):
        raise ValueError("Need both info and state; use /json, not /json/info")
    leds = info.get("leds", {})
    if not isinstance(leds, dict):
        raise ValueError("Invalid info.leds")
    seglc = leds.get("seglc", [])
    if not isinstance(seglc, list):
        seglc = []
    segments = state.get("seg", [])
    if not isinstance(segments, list):
        raise ValueError("Invalid state.seg")
    output = []
    for seg in segments:
        if not isinstance(seg, dict):
            raise ValueError("Invalid segment")
        sid = seg.get("id")
        valid_id = isinstance(sid, int) and not isinstance(sid, bool) and 0 <= sid < len(seglc)
        output.append({**{k: seg.get(k) for k in ("id", "on", "bri", "fx", "col", "cct")},
                       "capabilities": capabilities(seglc[sid]) if valid_id else None})
    return {"version": info.get("ver"), "led_count": leds.get("count"),
            "common_capabilities": capabilities(leds.get("lc")),
            "on": state.get("on"), "brightness": state.get("bri"), "segments": output}


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        raise ValueError("Redirect refused; use the controller's direct hostname")


def controller_url(host):
    parsed = urllib.parse.urlsplit("http://" + host)
    if not parsed.hostname or parsed.username or parsed.password or parsed.path or parsed.query or parsed.fragment:
        raise ValueError("Use a hostname or IP, optionally with port; no URL or credentials")
    _ = parsed.port  # validate the port
    return "http://" + parsed.netloc + "/json"


def read_host(host):
    # Do not route private controller reads through an environment-configured proxy.
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
    request = urllib.request.Request(controller_url(host), headers={"User-Agent": "HYW-WLED-Inspector/1.0"})
    with opener.open(request, timeout=5) as response:
        raw = response.read(1024 * 1024 + 1)
    if len(raw) > 1024 * 1024:
        raise ValueError("Response exceeds 1 MiB")
    return json.loads(raw)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--host", action="append", default=[], help="Explicit local WLED hostname/IP; repeat for each controller")
    parser.add_argument("--file", action="append", default=[], help="Saved /json response; never contacts a controller")
    args = parser.parse_args()
    if not args.host and not args.file:
        parser.error("provide --host or --file")
    results, failed = [], False
    for kind, target in [("host", x) for x in args.host] + [("file", x) for x in args.file]:
        try:
            if kind == "host":
                data = read_host(target)
            else:
                with open(target, encoding="utf-8") as handle:
                    data = json.load(handle)
            results.append({"input_number": len(results) + 1, **summarize(data)})
        except (OSError, ValueError) as error:
            failed = True
            # Never echo a URL, filesystem path or full raw server response.
            results.append({"input_number": len(results) + 1, "error": type(error).__name__,
                            "hint": "Check the host/file, network access, and /json response"})
    print(json.dumps(results, indent=2))
    return 1 if failed else 0


if __name__ == "__main__":
    sys.exit(main())
