"""CRUD for folders, datacenters, clusters, resource pools, hosts, DVS."""

from __future__ import annotations

from typing import Any

from app.db.pool import Database
from app.vsphere import inventory
from app.vsphere.errors import invalid_argument, not_found


async def create_folder(
    database: Database,
    *,
    name: str,
    parent: str,
    folder_type: str = "VIRTUAL_MACHINE",
) -> str:
    parent_obj = await inventory.get_object(database, parent)
    if parent_obj is None:
        raise not_found(f"Parent {parent} not found")
    moid = await inventory.next_moid(database, "group")
    # next_moid with group- may not work well; use folder- style
    import secrets

    moid = f"group-{secrets.token_hex(3)}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="Folder",
        name=name,
        parent_moid=parent,
        props={"folder_type": folder_type},
    )
    return moid


async def create_datacenter(database: Database, *, name: str, folder: str = "group-d1") -> str:
    import secrets

    parent_obj = await inventory.get_object(database, folder)
    if parent_obj is None:
        raise not_found(f"Parent {folder} not found")
    moid = f"datacenter-{secrets.randbelow(900) + 100}"
    host_folder = f"group-h{secrets.randbelow(90) + 10}"
    vm_folder = f"group-v{secrets.randbelow(90) + 10}"
    ds_folder = f"group-s{secrets.randbelow(90) + 10}"
    net_folder = f"group-n{secrets.randbelow(90) + 10}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="Datacenter",
        name=name,
        parent_moid=folder,
        props={
            "datastore_folder": ds_folder,
            "host_folder": host_folder,
            "vm_folder": vm_folder,
            "network_folder": net_folder,
        },
    )
    for child_moid, child_name, ftype in (
        (host_folder, "host", "HOST"),
        (vm_folder, "vm", "VIRTUAL_MACHINE"),
        (ds_folder, "datastore", "DATASTORE"),
        (net_folder, "network", "NETWORK"),
    ):
        await inventory.upsert_object(
            database,
            moid=child_moid,
            type_name="Folder",
            name=child_name,
            parent_moid=moid,
            props={"folder_type": ftype},
        )
    return moid


async def create_cluster(
    database: Database,
    *,
    name: str,
    folder: str = "group-h23",
    drs_enabled: bool = True,
    ha_enabled: bool = True,
) -> str:
    import secrets

    parent_obj = await inventory.get_object(database, folder)
    if parent_obj is None:
        raise not_found(f"Parent {folder} not found")
    moid = f"domain-c{secrets.randbelow(900) + 100}"
    rp = f"resgroup-{secrets.randbelow(900) + 100}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="ClusterComputeResource",
        name=name,
        parent_moid=folder,
        props={"drs_enabled": drs_enabled, "ha_enabled": ha_enabled, "resource_pool": rp},
    )
    await inventory.upsert_object(
        database,
        moid=rp,
        type_name="ResourcePool",
        name="Resources",
        parent_moid=moid,
        props={"cpu_limit_mhz": -1, "memory_limit_mib": -1},
    )
    return moid


async def create_resource_pool(
    database: Database,
    *,
    name: str,
    parent: str,
) -> str:
    parent_obj = await inventory.get_object(database, parent)
    if parent_obj is None:
        raise not_found(f"Parent {parent} not found")
    import secrets

    moid = f"resgroup-{secrets.randbelow(900) + 100}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="ResourcePool",
        name=name,
        parent_moid=parent,
        props={"cpu_limit_mhz": -1, "memory_limit_mib": -1},
    )
    return moid


async def rename_object(database: Database, moid: str, name: str) -> None:
    obj = await inventory.get_object(database, moid)
    if obj is None:
        raise not_found(f"Object {moid} not found")
    await inventory.upsert_object(
        database,
        moid=obj.moid,
        type_name=obj.type,
        name=name,
        parent_moid=obj.parent_moid,
        props=obj.props,
    )


async def move_object(database: Database, moid: str, parent: str) -> None:
    obj = await inventory.get_object(database, moid)
    if obj is None:
        raise not_found(f"Object {moid} not found")
    parent_obj = await inventory.get_object(database, parent)
    if parent_obj is None:
        raise not_found(f"Parent {parent} not found")
    await inventory.upsert_object(
        database,
        moid=obj.moid,
        type_name=obj.type,
        name=obj.name,
        parent_moid=parent,
        props=obj.props,
    )


async def delete_managed(database: Database, moid: str) -> None:
    obj = await inventory.get_object(database, moid)
    if obj is None:
        raise not_found(f"Object {moid} not found")
    # Protect the lab seed spine so surface/matrix probes cannot empty the inventory dump.
    if moid in _SEED_PROTECTED_MOIDS or _is_seed_host_or_named_vm(moid, obj):
        raise invalid_argument(f"Cannot delete protected seed object {moid}")
    children = [
        child for child in await inventory.list_objects(database) if child.parent_moid == moid
    ]
    if children:
        raise invalid_argument(f"Object {moid} still has children")
    await inventory.delete_object(database, moid)


# Stable MOIDs from profiles._topology / small named VMs (all seed sizes).
_SEED_PROTECTED_MOIDS = frozenset(
    {
        "group-d1",
        "datacenter-21",
        "group-h23",
        "group-v23",
        "group-s23",
        "group-n23",
        "domain-c21",
        "resgroup-22",
        "group-v100",
        "group-v101",
        "group-v102",
        "network-41",
        "dvs-51",
        *(f"datastore-{30 + n}" for n in range(1, 9)),
        *(f"dvportgroup-{40 + n}" for n in range(2, 9)),
        *(f"group-v{103 + n}" for n in range(0, 8)),
        "vm-101",
        "vm-102",
        "vm-103",
        "vm-104",
        "vm-105",
    }
)


def _is_seed_host_or_named_vm(moid: str, obj: Any) -> bool:
    if obj.type == "HostSystem" and moid.startswith("host-"):
        # Seed hosts are host-11..host-N; probe hosts use other patterns if any.
        try:
            return 11 <= int(moid.split("-", 1)[1]) <= 40
        except ValueError:
            return False
    return False


async def set_host_maintenance(database: Database, host: str, enabled: bool) -> dict[str, Any]:
    obj = await inventory.get_object(database, host)
    if obj is None or obj.type != "HostSystem":
        raise not_found(f"Host {host} not found")
    props = dict(obj.props)
    props["connection_state"] = "CONNECTED"
    props["maintenance_mode"] = enabled
    await inventory.update_props(database, host, props)
    return {"host": host, "maintenance_mode": enabled}


async def create_dvs(
    database: Database,
    *,
    name: str,
    folder: str = "group-n23",
) -> str:
    import secrets

    moid = f"dvs-{secrets.randbelow(900) + 100}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="VmwareDistributedVirtualSwitch",
        name=name,
        parent_moid=folder,
        props={"version": "8.0.0", "num_ports": 128},
    )
    return moid


async def create_dvpg(
    database: Database,
    *,
    name: str,
    dvs: str,
    vlan_id: int = 0,
) -> str:
    parent = await inventory.get_object(database, dvs)
    if parent is None:
        raise not_found(f"DVS {dvs} not found")
    import secrets

    moid = f"dvportgroup-{secrets.randbelow(900) + 100}"
    await inventory.upsert_object(
        database,
        moid=moid,
        type_name="DistributedVirtualPortgroup",
        name=name,
        parent_moid=dvs,
        props={"type": "DISTRIBUTED_PORTGROUP", "vlan_id": vlan_id, "dvs": dvs},
    )
    return moid
