"""Role / privilege authorization for vSphere REST (and SOAP gates)."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from functools import wraps
from typing import Any

from fastapi import Depends

from app.db.pool import Database
from app.dependencies import get_database
from app.vsphere.errors import unauthorized
from app.vsphere.security.session import SessionInfo, require_session

# Privilege catalog (subset of vSphere privilege ids).
PRIVILEGES: dict[str, str] = {
    "System.Anonymous": "Anonymous access",
    "System.Read": "Read inventory",
    "System.View": "View inventory",
    "Global.ManageCustomFields": "Manage custom fields",
    "Authorization.ModifyPermissions": "Modify permissions",
    "VirtualMachine.Inventory.Create": "Create VM",
    "VirtualMachine.Inventory.Delete": "Delete VM",
    "VirtualMachine.Inventory.Move": "Move VM",
    "VirtualMachine.Interact.PowerOn": "Power on VM",
    "VirtualMachine.Interact.PowerOff": "Power off VM",
    "VirtualMachine.Interact.Suspend": "Suspend VM",
    "VirtualMachine.Interact.Reset": "Reset VM",
    "VirtualMachine.Interact.DeviceConnection": "Connect devices",
    "VirtualMachine.Interact.ConsoleInteract": "Console",
    "VirtualMachine.Config.AddNewDisk": "Add disk",
    "VirtualMachine.Config.AddExistingDisk": "Add existing disk",
    "VirtualMachine.Config.RemoveDisk": "Remove disk",
    "VirtualMachine.Config.CPUCount": "Change CPU",
    "VirtualMachine.Config.Memory": "Change memory",
    "VirtualMachine.Config.AddRemoveDevice": "Add/remove device",
    "VirtualMachine.Config.Rename": "Rename VM",
    "VirtualMachine.Provisioning.Clone": "Clone VM",
    "VirtualMachine.Provisioning.DeployTemplate": "Deploy template",
    "VirtualMachine.Provisioning.MarkAsTemplate": "Mark as template",
    "VirtualMachine.State.CreateSnapshot": "Create snapshot",
    "VirtualMachine.State.RemoveSnapshot": "Remove snapshot",
    "VirtualMachine.State.RevertToSnapshot": "Revert snapshot",
    "Datastore.Browse": "Browse datastore",
    "Datastore.FileManagement": "Manage datastore files",
    "Host.Config.Maintenance": "Host maintenance",
    "Folder.Create": "Create folder",
    "Folder.Delete": "Delete folder",
    "Folder.Rename": "Rename folder",
    "Folder.Move": "Move folder",
    "Datacenter.Create": "Create datacenter",
    "Datacenter.Delete": "Delete datacenter",
    "Cluster.Create": "Create cluster",
    "Cluster.Delete": "Delete cluster",
    "Resource.CreatePool": "Create resource pool",
    "Resource.DeletePool": "Delete resource pool",
    "Network.Assign": "Assign network",
    "ContentLibrary.CreateLocalLibrary": "Create content library",
    "ContentLibrary.AddLibraryItem": "Add library item",
    "InventoryService.Tagging.CreateCategory": "Create tag category",
    "InventoryService.Tagging.CreateTag": "Create tag",
    "InventoryService.Tagging.AttachTag": "Attach tag",
}

_ALL = frozenset(PRIVILEGES)
_READ = frozenset({"System.Anonymous", "System.Read", "System.View", "Datastore.Browse"})
_POWER = frozenset(
    {
        *_READ,
        "VirtualMachine.Interact.PowerOn",
        "VirtualMachine.Interact.PowerOff",
        "VirtualMachine.Interact.Suspend",
        "VirtualMachine.Interact.Reset",
        "VirtualMachine.Interact.ConsoleInteract",
        "VirtualMachine.State.CreateSnapshot",
        "VirtualMachine.State.RemoveSnapshot",
        "VirtualMachine.State.RevertToSnapshot",
        "VirtualMachine.Provisioning.Clone",
    }
)
_VM_ADMIN = frozenset(
    {
        *_POWER,
        "VirtualMachine.Inventory.Create",
        "VirtualMachine.Inventory.Delete",
        "VirtualMachine.Inventory.Move",
        "VirtualMachine.Config.AddNewDisk",
        "VirtualMachine.Config.AddExistingDisk",
        "VirtualMachine.Config.RemoveDisk",
        "VirtualMachine.Config.CPUCount",
        "VirtualMachine.Config.Memory",
        "VirtualMachine.Config.AddRemoveDevice",
        "VirtualMachine.Config.Rename",
        "VirtualMachine.Provisioning.DeployTemplate",
        "VirtualMachine.Provisioning.MarkAsTemplate",
        "VirtualMachine.Interact.DeviceConnection",
        "Datastore.FileManagement",
        "Network.Assign",
        "InventoryService.Tagging.CreateCategory",
        "InventoryService.Tagging.CreateTag",
        "InventoryService.Tagging.AttachTag",
        "ContentLibrary.CreateLocalLibrary",
        "ContentLibrary.AddLibraryItem",
    }
)

ROLE_PRIVILEGES: dict[str, frozenset[str]] = {
    "Administrator": _ALL,
    "ReadOnly": _READ,
    "VirtualMachinePowerUser": _POWER,
    "VirtualMachineAdministrator": _VM_ADMIN,
}


def privileges_for_roles(roles: list[str] | tuple[str, ...]) -> frozenset[str]:
    granted: set[str] = set()
    for role in roles:
        granted.update(ROLE_PRIVILEGES.get(role, ()))
    return frozenset(granted)


def has_privilege(roles: list[str] | tuple[str, ...], privilege: str) -> bool:
    granted = privileges_for_roles(roles)
    if privilege in granted:
        return True
    # Wildcard Administrator already has exact set; keep prefix convenience.
    return any(p.endswith(".*") and privilege.startswith(p[:-1]) for p in granted)


async def load_roles(database: Database, username: str) -> list[str]:
    pool = database.pool  # type: ignore[attr-defined]
    async with pool.acquire() as conn:
        row = await conn.fetchrow(
            "SELECT roles FROM vsphere_credentials WHERE username = $1",
            username,
        )
    if row is None:
        return ["Administrator"] if username.endswith("@vsphere.local") else ["ReadOnly"]
    roles = list(row["roles"] or [])
    return roles or ["ReadOnly"]


def require_privilege(*needed: str) -> Callable[..., Any]:
    """FastAPI dependency factory: session must hold every listed privilege."""

    async def _dependency(
        session: SessionInfo = Depends(require_session),
        database: Database = Depends(get_database),
    ) -> SessionInfo:
        roles = list(session.roles)
        if not roles:
            roles = await load_roles(database, session.username)
        for privilege in needed:
            if not has_privilege(roles, privilege):
                raise unauthorized(f"Missing privilege: {privilege}")
        return session

    return _dependency


require_read = require_privilege("System.Read")
require_power = require_privilege("VirtualMachine.Interact.PowerOn")
require_vm_mutate = require_privilege("VirtualMachine.Inventory.Create")
require_admin = require_privilege("Authorization.ModifyPermissions")


def guard(*needed: str) -> Callable[[Callable[..., Awaitable[Any]]], Callable[..., Awaitable[Any]]]:
    """Decorator-style helper for non-FastAPI call sites (SOAP)."""

    def decorator(fn: Callable[..., Awaitable[Any]]) -> Callable[..., Awaitable[Any]]:
        @wraps(fn)
        async def wrapper(*args: Any, **kwargs: Any) -> Any:
            return await fn(*args, **kwargs)

        wrapper.__vsphere_privileges__ = needed  # type: ignore[attr-defined]
        return wrapper

    return decorator
