"""Per-major vSphere REST availability matrix (stub OpenAPI stand-in)."""

from __future__ import annotations

import json
import re
from functools import lru_cache
from pathlib import Path
from typing import Any

from app.vsphere.rest.coverage import ACTIVE_STATUSES, IMPLEMENTED

# Integer majors mirror the console hot-swap ids (6-9).
VERSIONS: dict[int, dict[str, str]] = {
    6: {"series": "vSphere 7.0", "version": "7.0.0"},
    7: {"series": "vSphere 7.0 U3", "version": "7.0.3"},
    8: {"series": "vSphere 8.0", "version": "8.0.0"},
    9: {"series": "vSphere 8.0 U2", "version": "8.0.2"},
}

# Minimum major at which a registered path is considered implemented.
# Anything absent defaults to major 9 (latest) so new paths stay opt-in until catalogued.
_DEFAULT_FLOOR = 9

PATH_FLOOR: dict[tuple[str, str], int] = {
    # Always-on core (7.0+)
    ("POST", "/api/session"): 6,
    ("DELETE", "/api/session"): 6,
    ("GET", "/api/session"): 6,
    ("POST", "/rest/com/vmware/cis/session"): 6,
    ("GET", "/rest/com/vmware/cis/session"): 6,
    ("DELETE", "/rest/com/vmware/cis/session"): 6,
    ("GET", "/api/appliance/system/version"): 6,
    ("GET", "/api/vcenter/vm"): 6,
    ("POST", "/api/vcenter/vm"): 6,
    ("GET", "/api/vcenter/vm/{vm}"): 6,
    ("DELETE", "/api/vcenter/vm/{vm}"): 6,
    ("POST", "/api/vcenter/vm/{vm}/power"): 6,
    ("GET", "/api/vcenter/vm/{vm}/guest/identity"): 6,
    ("GET", "/api/vcenter/host"): 6,
    ("GET", "/api/vcenter/host/{host}"): 6,
    ("GET", "/api/vcenter/datastore"): 6,
    ("GET", "/api/vcenter/datastore/{datastore}"): 6,
    ("GET", "/api/vcenter/network"): 6,
    ("GET", "/api/vcenter/datacenter"): 6,
    ("GET", "/api/vcenter/cluster"): 6,
    ("GET", "/api/vcenter/folder"): 6,
    ("GET", "/api/vcenter/resource-pool"): 6,
    # 7.0 U3 depth
    ("GET", "/api/cis/tasks"): 7,
    ("GET", "/api/cis/tasks/{task}"): 7,
    ("POST", "/api/cis/tasks"): 7,
    ("GET", "/api/vcenter/vm/{vm}/tools"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware/cpu"): 7,
    ("PATCH", "/api/vcenter/vm/{vm}/hardware/cpu"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware/memory"): 7,
    ("PATCH", "/api/vcenter/vm/{vm}/hardware/memory"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware/disk"): 7,
    ("POST", "/api/vcenter/vm/{vm}/hardware/disk"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware/ethernet"): 7,
    ("POST", "/api/vcenter/vm/{vm}/hardware/ethernet"): 7,
    ("GET", "/api/vcenter/vm/{vm}/hardware/boot"): 7,
    ("GET", "/api/vcenter/vm/{vm}/snapshots"): 7,
    ("POST", "/api/vcenter/vm/{vm}/snapshots"): 7,
    ("DELETE", "/api/vcenter/vm/{vm}/snapshots/{snapshot}"): 7,
    ("POST", "/api/vcenter/vm/{vm}/snapshots/{snapshot}"): 7,
    ("POST", "/api/vcenter/vm/{vm}/clone"): 7,
    ("POST", "/api/vcenter/vm/{vm}/relocate"): 7,
    ("POST", "/api/vcenter/host/{host}/maintenance"): 7,
    ("GET", "/api/vcenter/datastore/{datastore}/files"): 7,
    ("POST", "/api/vcenter/datastore/{datastore}/files"): 7,
    ("POST", "/api/vcenter/datacenter"): 7,
    ("DELETE", "/api/vcenter/datacenter/{datacenter}"): 7,
    ("POST", "/api/vcenter/cluster"): 7,
    ("DELETE", "/api/vcenter/cluster/{cluster}"): 7,
    ("POST", "/api/vcenter/folder"): 7,
    ("POST", "/api/vcenter/folder/{folder}"): 7,
    ("DELETE", "/api/vcenter/folder/{folder}"): 7,
    ("POST", "/api/vcenter/resource-pool"): 7,
    ("DELETE", "/api/vcenter/resource-pool/{resource_pool}"): 7,
    ("GET", "/api/cis/tagging/category"): 7,
    ("POST", "/api/cis/tagging/category"): 7,
    ("GET", "/api/cis/tagging/category/{category_id}"): 7,
    ("DELETE", "/api/cis/tagging/category/{category_id}"): 7,
    ("GET", "/api/cis/tagging/tag"): 7,
    ("POST", "/api/cis/tagging/tag"): 7,
    ("GET", "/api/cis/tagging/tag/{tag_id}"): 7,
    ("DELETE", "/api/cis/tagging/tag/{tag_id}"): 7,
    ("POST", "/api/cis/tagging/tag-association"): 7,
    # 8.0 platform services
    ("GET", "/api/appliance/health/system"): 8,
    ("GET", "/api/appliance/networking"): 8,
    ("GET", "/api/appliance/timesync"): 8,
    ("GET", "/api/vcenter/network/dvs"): 8,
    ("POST", "/api/vcenter/network/dvs"): 8,
    ("POST", "/api/vcenter/network/dvpg"): 8,
    ("GET", "/api/content/library"): 8,
    ("GET", "/api/content/library/{library_id}"): 8,
    ("GET", "/api/content/local-library"): 8,
    ("POST", "/api/content/local-library"): 8,
    ("GET", "/api/content/local-library/{library_id}"): 8,
    ("DELETE", "/api/content/local-library/{library_id}"): 8,
    ("GET", "/api/content/library/item"): 8,
    ("POST", "/api/content/library/item"): 8,
    ("GET", "/api/content/library/item/{library_item_id}"): 8,
    ("DELETE", "/api/content/library/item/{library_item_id}"): 8,
    ("POST", "/api/vcenter/ovf/library-item/{item_id}"): 8,
    ("GET", "/api/vcenter/storage/policies"): 8,
    ("GET", "/api/vcenter/storage/policies/{policy}/vm"): 8,
    ("GET", "/api/vcenter/privilege"): 8,
    ("GET", "/api/vcenter/authorization/roles"): 8,
    ("GET", "/api/vcenter/authorization/permissions"): 8,
    ("POST", "/api/vcenter/authorization/permissions"): 8,
    ("DELETE", "/api/vcenter/authorization/permissions/{permission_id}"): 8,
    ("GET", "/api/vcenter/identity/providers"): 8,
    ("GET", "/api/vcenter/certificate-management/vcenter/tls"): 9,
    ("GET", "/api/vcenter/vm/{vm}/guest/networking"): 7,
    ("GET", "/api/vcenter/vm/{vm}/guest/power"): 7,
    ("POST", "/api/vcenter/vm/{vm}/guest/power"): 7,
    ("POST", "/api/vcenter/vm/{vm}/tools"): 7,
    ("POST", "/api/vcenter/vm/{vm}/console/tickets"): 8,
    ("POST", "/api/vcenter/vm/{vm}/guest/customization"): 8,
    ("POST", "/api/vcenter/vm/{vm}"): 7,
    ("GET", "/api/vcenter/host/{host}/storage/storage-device"): 8,
    ("GET", "/api/vcenter/host/{host}/networking"): 8,
    ("GET", "/api/vcenter/folder/{folder}/children"): 7,
    ("GET", "/api/vapi/metadata/metamodel/service"): 8,
    ("GET", "/api/vapi/metadata/authentication/component"): 8,
    ("GET", "/api/vcenter/activity-history"): 8,
    ("GET", "/rest/vcenter/vm"): 6,
    ("GET", "/rest/vcenter/vm/{vm}"): 6,
    ("POST", "/rest/vcenter/vm/{vm}/power"): 6,
    ("GET", "/rest/vcenter/host"): 6,
    ("GET", "/rest/vcenter/datastore"): 6,
    ("GET", "/rest/vcenter/network"): 6,
    ("GET", "/rest/vcenter/datacenter"): 6,
    ("GET", "/rest/vcenter/cluster"): 6,
    ("GET", "/rest/appliance/system/version"): 6,
}


def floor_for(verb: str, path: str) -> int:
    return PATH_FLOOR.get((verb.upper(), path), _DEFAULT_FLOOR)


def methods_for_major(major: int) -> dict[tuple[str, str], str]:
    active = major if major in VERSIONS else 9
    return {
        key: status for key, status in IMPLEMENTED.items() if floor_for(key[0], key[1]) <= active
    }


def catalog_entries_for_major(major: int) -> list[dict[str, str]]:
    return [
        {"verb": verb, "path": path, "status": status}
        for (verb, path), status in sorted(methods_for_major(major).items())
    ]


def is_implemented_for_major(verb: str, path: str, major: int) -> bool:
    return methods_for_major(major).get((verb.upper(), path)) in ACTIVE_STATUSES


@lru_cache(maxsize=1)
def _compiled_routes() -> list[tuple[str, re.Pattern[str], str, int, int]]:
    compiled: list[tuple[str, re.Pattern[str], str, int, int]] = []
    for verb, path in IMPLEMENTED:
        pattern = "^" + re.sub(r"\{[^/]+\}", r"[^/]+", path) + "$"
        parts = [part for part in path.split("/") if part]
        static = sum(1 for part in parts if not (part.startswith("{") and part.endswith("}")))
        compiled.append((verb, re.compile(pattern), path, static, len(path)))
    # Prefer more literal segments so /library/item beats /library/{library_id}.
    compiled.sort(key=lambda item: (item[0], -item[3], -item[4], item[2]))
    return compiled


def resolve_template(verb: str, request_path: str) -> str | None:
    method = verb.upper()
    for route_verb, pattern, template, _static, _length in _compiled_routes():
        if route_verb == method and pattern.match(request_path):
            return template
    return None


def available_for_request(verb: str, request_path: str, major: int) -> bool | None:
    """True = allowed, False = known but wrong version, None = not in registry.

    Lab policy: every registered Automation API method is always served with real
    (DB-backed) handlers/stubs regardless of the hot-swapped catalog major.
    Catalog browse still uses ``methods_for_major`` / PATH_FLOOR for history.
    """

    del major  # major retained for call-site compatibility; gating is catalog-only
    template = resolve_template(verb, request_path)
    if template is None:
        return None
    return True


def bundle_payload(major: int) -> dict[str, Any]:
    meta = VERSIONS.get(major) or VERSIONS[9]
    entries = catalog_entries_for_major(major)
    return {
        "product": "vmware-api-simulator",
        "plane": "vsphere-rest",
        "major": major,
        "series": meta["series"],
        "version": meta["version"],
        "kind": "stub-openapi-matrix",
        "notes": (
            "Stub contract derived from app/vsphere/rest/coverage.py + PATH_FLOOR. "
            "Not a Broadcom OpenAPI dump; used for catalog browse and hot-swap gating."
        ),
        "method_count": len(entries),
        "methods": entries,
    }


def bundles_root() -> Path:
    return Path(__file__).resolve().parents[3] / "contracts" / "vsphere"


def write_bundles(root: Path | None = None) -> list[Path]:
    base = root or bundles_root()
    written: list[Path] = []
    for major in sorted(VERSIONS):
        meta = VERSIONS[major]
        directory = base / meta["version"]
        directory.mkdir(parents=True, exist_ok=True)
        payload = bundle_payload(major)
        path = directory / "manifest.json"
        path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
        written.append(path)
    (base / "README.md").write_text(
        "# vSphere stub contracts\n\n"
        "Versioned JSON matrices generated from `app/vsphere/contracts/matrix.py`.\n"
        "Hot-swap (`POST /ui/api/contract/apply?major=N`) switches the catalog major "
        "for UI browse/evidence. Runtime always serves the full registered surface "
        "(no HTTP 501 version gate on known paths).\n",
        encoding="utf-8",
    )
    return written


def load_bundle(major: int) -> dict[str, Any]:
    meta = VERSIONS.get(major) or VERSIONS[9]
    path = bundles_root() / meta["version"] / "manifest.json"
    if path.is_file():
        return json.loads(path.read_text(encoding="utf-8"))
    return bundle_payload(major)
