#!/usr/bin/env python3
"""Audit issue 62's native UDP inventory-manager boundary."""

from __future__ import annotations

from pathlib import Path
import re

import generate_api_shims


ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "crates" / "libremetaverse" / "src" / "inventory_manager.rs"
DOC = ROOT / "docs" / "inventory.md"
MANAGER_TESTS = ROOT / "tests" / "compat" / "tests" / "inventory_manager_semantics.rs"
TASK_TESTS = ROOT / "tests" / "compat" / "tests" / "task_inventory_semantics.rs"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")

REQUIRED_TYPES = {
    "T:LibreMetaverse.FindObjectByPathReplyEventArgs",
    "T:LibreMetaverse.FolderUpdatedEventArgs",
    "T:LibreMetaverse.InventoryManager",
    "T:LibreMetaverse.InventoryManager.CopyItemsResult",
    "T:LibreMetaverse.InventoryManager.FolderUpdateResult",
    "T:LibreMetaverse.InventoryObjectOfferedEventArgs",
    "T:LibreMetaverse.ItemReceivedEventArgs",
    "T:LibreMetaverse.SaveAssetToInventoryEventArgs",
    "T:LibreMetaverse.ScriptRunningReplyEventArgs",
    "T:LibreMetaverse.TaskInventoryReplyEventArgs",
    "T:LibreMetaverse.TaskItemReceivedEventArgs",
}

REQUIRED_MEMBERS = {
    "E:LibreMetaverse.InventoryManager.FindObjectByPathReply",
    "E:LibreMetaverse.InventoryManager.FolderUpdated",
    "E:LibreMetaverse.InventoryManager.InventoryObjectOffered",
    "E:LibreMetaverse.InventoryManager.ItemReceived",
    "E:LibreMetaverse.InventoryManager.ScriptRunningReply",
    "E:LibreMetaverse.InventoryManager.TaskInventoryReply",
    "E:LibreMetaverse.InventoryManager.TaskItemReceived",
    "M:LibreMetaverse.InventoryManager.#ctor(LibreMetaverse.GridClient)",
    "M:LibreMetaverse.InventoryManager.CreateFolder(LibreMetaverse.UUID,System.String,LibreMetaverse.FolderType)",
    "M:LibreMetaverse.InventoryManager.CreateItemAsync(LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,LibreMetaverse.InventoryType,LibreMetaverse.WearableType,LibreMetaverse.PermissionMask,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.CreateLinkAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.String,LibreMetaverse.InventoryType,LibreMetaverse.UUID,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.FetchItemAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.FindObjectByPathAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.String,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)",
    "M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.GiveFolderAsync(LibreMetaverse.UUID,System.String,LibreMetaverse.UUID,System.Boolean,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.GiveItem(LibreMetaverse.UUID,System.String,LibreMetaverse.AssetType,LibreMetaverse.UUID,System.Boolean)",
    "M:LibreMetaverse.InventoryManager.MoveFolders(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.MoveItemsAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.RemoveItemsAsync(System.Collections.Generic.IEnumerable{LibreMetaverse.UUID},System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.RequestCopyItemsWithResultAsync(System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{LibreMetaverse.UUID},System.Collections.Generic.List{System.String},LibreMetaverse.UUID,System.Threading.CancellationToken)",
    "M:LibreMetaverse.InventoryManager.RequestDeRezToInventory(System.UInt32,LibreMetaverse.DeRezDestination,LibreMetaverse.UUID,LibreMetaverse.UUID)",
    "M:LibreMetaverse.InventoryManager.RequestFetchInventoryAsync(System.Collections.Generic.Dictionary{LibreMetaverse.UUID,LibreMetaverse.UUID},System.Threading.CancellationToken,System.Action{System.Collections.Generic.List{LibreMetaverse.InventoryItem}})",
    "M:LibreMetaverse.InventoryManager.RequestRezFromInventory(LibreMetaverse.Simulator,LibreMetaverse.UUID,LibreMetaverse.Quaternion,LibreMetaverse.Vector3,LibreMetaverse.InventoryItem,LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean)",
    "M:LibreMetaverse.InventoryManager.RequestTaskInventory(System.UInt32,LibreMetaverse.Simulator)",
    "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem},LibreMetaverse.UUID)",
    "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)",
    "M:LibreMetaverse.InventoryManager.WaitForNextInventoryOfferAsync(System.Threading.CancellationToken)",
}


def require_markers(path: Path, markers: tuple[str, ...]) -> None:
    source = path.read_text()
    missing = [marker for marker in markers if marker not in source]
    if missing:
        raise SystemExit(f"{path.name}: audit evidence missing: " + ", ".join(missing))


def main() -> None:
    native_types = (
        generate_api_shims.NATIVE_TYPES.keys()
        | generate_api_shims.NATIVE_DECLARATIONS.keys()
    )
    missing_types = sorted(REQUIRED_TYPES - native_types)
    if missing_types:
        raise SystemExit("issue 62 native types missing: " + ", ".join(missing_types))
    missing_members = sorted(
        REQUIRED_MEMBERS - generate_api_shims.NATIVE_MEMBER_BODIES.keys()
    )
    if missing_members:
        raise SystemExit("issue 62 native members missing: " + ", ".join(missing_members))
    update_task = (
        "M:LibreMetaverse.InventoryManager.UpdateTaskInventory(System.UInt32,"
        "LibreMetaverse.InventoryItem,LibreMetaverse.Simulator,System.Boolean)"
    )
    if "only_mod_meta.unwrap_or(true)" not in generate_api_shims.NATIVE_MEMBER_BODIES[update_task]:
        raise SystemExit("UpdateTaskInventory must retain the C# metadata-only default")
    for update_member in (
        "M:LibreMetaverse.InventoryManager.RequestUpdateItem(LibreMetaverse.InventoryItem)",
        "M:LibreMetaverse.InventoryManager.RequestUpdateItems(System.Collections.Generic.List{LibreMetaverse.InventoryItem})",
    ):
        if "UUID::random()?" not in generate_api_shims.NATIVE_MEMBER_BODIES[update_member]:
            raise SystemExit(f"{update_member} must generate a transaction ID")
    if STUB_RE.search(SOURCE.read_text()):
        raise SystemExit("issue 62 owned Rust stubs remain in inventory_manager.rs")

    require_markers(
        SOURCE,
        (
            "MAX_TASK_INVENTORY_BYTES",
            "MAX_PENDING_REQUESTS",
            "encode_multiple",
            "pending_created",
            "pending_fetches",
            "pending_task_replies",
            "callbacks are not invoked while an inventory lock is held",
            "cancelling_one_fetch_waiter_preserves_concurrent_correlation",
            "fake_bulk_copy_reply_waits_for_every_block",
            "asset_type_without_folder_type_falls_back_to_inventory_root",
            "stale_descendents_packet_cannot_mutate_store",
            "task_xfer_rejects_announced_data_above_bound",
        ),
    )
    require_markers(
        DOC,
        (
            "UDP inventory manager",
            "4,096 pending operations",
            "16 MiB",
            "RequestXfer",
            "No store lock is held across an await",
        ),
    )
    require_markers(
        MANAGER_TESTS,
        (
            "inventory_manager_null_throws",
            "encrypt_decrypt_round_trip",
            "item_crc_is_deterministic_and_changes_when_fields_change",
            "create_inventory_item_returns_correct_subclass",
        ),
    )
    require_markers(
        TASK_TESTS,
        (
            "cap_available_returns_contents_folder_and_decrypts_shadow_id",
            "plain_asset_id_parses_without_decryption",
            "non_success_status_returns_contents_folder_only",
            "no_capability_does_not_make_http_request",
        ),
    )
    print(
        "issue 62 audit: native inventory packet mappings, bounded correlation and Xfer, "
        "lock-free callback evidence, documentation, and translated compatibility cases are present"
    )


if __name__ == "__main__":
    main()
