#!/usr/bin/env python3
"""Fail if any registered GET returns 501, stub marker, or empty JSON body."""

from __future__ import annotations

import json
import os
import re
import ssl
import sys
import urllib.error
import urllib.request
from base64 import b64encode

from app.vsphere.rest.coverage import IMPLEMENTED

BASE = os.getenv("VSPHERE_BASE", "https://localhost")
USER = os.getenv("VSPHERE_USER", "administrator@vsphere.local")
PASSWORD = os.getenv("VSPHERE_PASSWORD", "VMware1!")

_SUBS = {
    "{vm}": "vm-101",
    "{host}": "host-11",
    "{datastore}": "datastore-31",
    "{task}": "task-1",
    "{snapshot}": "snapshot-missing",
    "{category_id}": "cat-lab-1",
    "{tag_id}": "tag-lab-1",
    "{item_id}": "item-ubuntu",
    "{library_id}": "lib-local-1",
    "{folder}": "group-v23",
    "{datacenter}": "datacenter-21",
    "{cluster}": "domain-c21",
    "{resource_pool}": "resgroup-22",
    "{permission_id}": "1",
    "{policy}": "policy-default",
    "{disk}": "2000",
    "{nic}": "4000",
    "{cdrom}": "3000",
    "{adapter}": "1000",
    "{network}": "network-41",
    "{supervisor}": "supervisor-1",
    "{commit}": "commit-lab-1",
    "{domain}": "lab.local",
    "{interface}": "nic0",
    "{service}": "vpxd",
    "{session_id}": "session-lab-1",
    "{download_session_id}": "session-lab-1",
    "{update_session_id}": "session-lab-1",
    "{provider}": "vsphere.local",
    "{role}": "ReadOnly",
    "{chain}": "chain-1",
    "{floppy}": "8000",
    "{port}": "9000",
}


def _ctx() -> ssl.SSLContext | None:
    if not BASE.startswith("https://"):
        return None
    return ssl._create_unverified_context()  # noqa: S323


def _concrete(path: str) -> str:
    out = path
    for key, value in _SUBS.items():
        out = out.replace(key, value)
    return re.sub(r"\{([A-Za-z0-9_]+)\}", r"lab-\1", out)


def _request(method: str, path: str, *, headers: dict[str, str]) -> tuple[int, str]:
    req = urllib.request.Request(f"{BASE}{path}", method=method, headers=headers)
    try:
        with urllib.request.urlopen(req, context=_ctx(), timeout=60) as resp:  # noqa: S310
            return int(resp.status), resp.read().decode("utf-8", errors="replace")
    except urllib.error.HTTPError as error:
        return int(error.code), error.read().decode("utf-8", errors="replace")


def main() -> int:
    basic = b64encode(f"{USER}:{PASSWORD}".encode()).decode()
    code, body = _request("POST", "/api/session", headers={"Authorization": f"Basic {basic}"})
    if code not in {200, 201}:
        print(json.dumps({"error": f"session failed {code}", "body": body[:200]}))
        return 1
    session = json.loads(body)
    headers = {"vmware-api-session-id": session, "Accept": "application/json"}
    failures: list[dict[str, object]] = []
    ok = 0
    checked = 0
    for (verb, path), _status in sorted(IMPLEMENTED.items()):
        if verb != "GET" or path in {"/api/session", "/rest/com/vmware/cis/session"}:
            continue
        checked += 1
        concrete = _concrete(path)
        if path == "/api/content/library/item":
            concrete = f"{concrete}?library_id=lib-local-1"
        status, raw = _request("GET", concrete, headers=headers)
        if status == 501:
            failures.append({"path": path, "status": status, "reason": "version gate 501"})
            continue
        if status >= 500:
            failures.append(
                {"path": path, "status": status, "reason": "server error", "body": raw[:160]}
            )
            continue
        if status not in {200, 201}:
            # Missing probe ids may 404 — still require a JSON error body.
            if not raw.strip():
                failures.append({"path": path, "status": status, "reason": "empty error body"})
            continue
        if not raw.strip():
            failures.append({"path": path, "status": status, "reason": "empty body"})
            continue
        if '"stub": true' in raw or '"stub":true' in raw:
            failures.append({"path": path, "status": status, "reason": "stub marker"})
            continue
        try:
            payload = json.loads(raw)
        except json.JSONDecodeError:
            failures.append(
                {"path": path, "status": status, "reason": "non-json", "body": raw[:160]}
            )
            continue
        if payload in ([], {}, None, "") or (
            isinstance(payload, (list, dict)) and len(payload) == 0
        ):
            failures.append(
                {"path": path, "status": status, "reason": "empty json", "body": raw[:160]}
            )
            continue
        if isinstance(payload, dict):
            for key in ("data", "value", "messages", "items", "results"):
                if key in payload and payload[key] in ([], None, {}):
                    failures.append(
                        {
                            "path": path,
                            "status": status,
                            "reason": f"empty nested {key}",
                            "body": raw[:160],
                        }
                    )
                    break
            else:
                ok += 1
            continue
        ok += 1
    print(
        json.dumps(
            {
                "checked": checked,
                "ok": ok,
                "failure_count": len(failures),
                "failures": failures[:80],
            },
            indent=2,
        )
    )
    return 1 if failures else 0


if __name__ == "__main__":
    raise SystemExit(main())
