#!/usr/bin/env python3
"""Classify MAAS fabrics for safe pruning (pure; no mutation, no network).

Inputs are the three JSON blobs produced by:
    maas <profile> fabrics  read   ->  fabrics.json
    maas <profile> subnets  read   ->  subnets.json
    maas <profile> machines read   ->  machines.json

SAFETY PREDICATE (delete iff ALL hold):
  * fabric name matches ^fabric-[0-9]+$  (an auto-minted commissioning fabric), AND
  * ZERO subnets are attached to the fabric, AND
  * ZERO machine interfaces are attached to the fabric.

Everything else is kept:
  * named / renamed fabrics (1_provider, 2_metal, f_oob, ...) -> never deleted;
  * an auto-fabric that still carries a subnet (e.g. an LXD/substrate bridge:
    10.37.x.0/24 + fd42::/64) -> never deleted (substrate, not a deploy artifact);
  * an auto-fabric still holding an interface -> WAIT (a host NIC the carve has not
    yet relocated); deletable only after the carve vacates it.

Output (stdout): deterministic JSON {"audit":[...], "delete_ids":[...]} sorted by
fabric id. delete_ids is the safe set.

Field shape (verified against MAAS 3.7 live output):
  fabrics[].id, fabrics[].name
  subnets[].vlan.fabric_id
  machines[].interface_set[].vlan.fabric_id   (present even for link_up-only NICs)
"""
import json
import re
import sys

AUTO = re.compile(r'^fabric-[0-9]+$')


def _count_by_fabric_subnets(subnets):
    out = {}
    for s in subnets:
        fid = (s.get("vlan") or {}).get("fabric_id")
        if fid is not None:
            out[fid] = out.get(fid, 0) + 1
    return out


def _count_by_fabric_ifaces(machines):
    out = {}
    for m in machines:
        for i in (m.get("interface_set") or []):
            fid = (i.get("vlan") or {}).get("fabric_id")
            if fid is not None:
                out[fid] = out.get(fid, 0) + 1
    return out


def classify(fabrics, subnets, machines):
    sub = _count_by_fabric_subnets(subnets)
    iff = _count_by_fabric_ifaces(machines)
    audit, delete_ids = [], []
    for f in sorted(fabrics, key=lambda x: x["id"]):
        fid = f["id"]
        name = f.get("name", "")
        ns = sub.get(fid, 0)
        ni = iff.get(fid, 0)
        auto = bool(AUTO.match(name))
        if not auto:
            verdict = "KEEP (named/default)"
        elif ns > 0:
            # auto-name but carries a subnet: substrate (e.g. LXD bridge) -- never delete
            verdict = "KEEP auto-fabric HAS SUBNET(S) -- substrate/in-use, never delete"
        elif ni > 0:
            verdict = "WAIT auto-fabric in use by interface(s) -- vacate (carve) before prune"
        else:
            verdict = "ORPHAN -- delete"
            delete_ids.append(fid)
        audit.append({
            "id": fid, "name": name, "subnets": ns, "ifaces": ni,
            "auto": auto, "verdict": verdict,
        })
    return {"audit": audit, "delete_ids": delete_ids}


def _load(path):
    with open(path) as fh:
        return json.load(fh)


def main(argv):
    if len(argv) != 4:
        sys.stderr.write(
            "usage: maas_fabric_classify.py <fabrics.json> <subnets.json> <machines.json>\n")
        return 2
    out = classify(_load(argv[1]), _load(argv[2]), _load(argv[3]))
    print(json.dumps(out, indent=2, sort_keys=True))
    return 0


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