#!/usr/bin/env python3
"""Audit issue 66's appearance baking and initial-outfit boundary."""

from __future__ import annotations

from pathlib import Path
import re

import generate_api_shims


ROOT = Path(__file__).resolve().parents[1]
APPEARANCE = ROOT / "crates" / "libremetaverse" / "src" / "appearance_manager.rs"
BAKER = ROOT / "crates" / "libremetaverse" / "src" / "appearance_baker.rs"
PROVIDER = ROOT / "crates" / "libremetaverse" / "src" / "baking_texture_provider.rs"
OUTFIT = ROOT / "crates" / "libremetaverse" / "src" / "initial_outfit.rs"
ASSETS = ROOT / "crates" / "libremetaverse" / "src" / "asset_models.rs"
BAKER_TESTS = ROOT / "crates" / "libremetaverse" / "src" / "appearance_baker_semantics.rs"
VISUAL_TESTS = ROOT / "crates" / "libremetaverse" / "tests" / "generated_catalogs.rs"
COMPAT = ROOT / "tests" / "compat" / "tests" / "appearance_semantics.rs"
DOC = ROOT / "docs" / "appearance.md"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")

REQUIRED_TYPES = {
    "T:LibreMetaverse.AppearanceManager",
    "T:LibreMetaverse.Appearance.InitialOutfit",
    "T:LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress",
    "T:LibreMetaverse.Appearance.InitialOutfitHandler",
    "T:LibreMetaverse.GridClientBakingTextureProvider",
    "T:LibreMetaverse.Imaging.Baker",
}

REQUIRED_MEMBERS = {
    "M:LibreMetaverse.AppearanceManager.DecodeWearableParams(LibreMetaverse.AppearanceManager.WearableData,LibreMetaverse.AppearanceManager.TextureData[]@)",
    "M:LibreMetaverse.AppearanceManager.MakeAppearancePacket",
    "M:LibreMetaverse.AppearanceManager.RequestCachedBakes",
    "M:LibreMetaverse.AppearanceManager.RequestSetAppearance(System.Boolean)",
    "M:LibreMetaverse.AppearanceManager.ServerBakingRegion",
    "M:LibreMetaverse.GridClientBakingTextureProvider.RequestTextureAsync(LibreMetaverse.UUID,System.Threading.CancellationToken)",
    "M:LibreMetaverse.Imaging.Baker.Bake",
    "M:LibreMetaverse.Imaging.Baker.LoadResourceLayer(System.String)",
    "M:LibreMetaverse.Appearance.InitialOutfit.CopyFolderAsync(LibreMetaverse.InventoryFolder,LibreMetaverse.UUID,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})",
    "M:LibreMetaverse.Appearance.InitialOutfit.SetInitialOutfitAsync(System.String,System.Threading.CancellationToken,System.IProgress{LibreMetaverse.Appearance.InitialOutfit.InitialOutfitProgress})",
}


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


def main() -> None:
    native = set(generate_api_shims.NATIVE_TYPES) | set(generate_api_shims.NATIVE_DECLARATIONS)
    missing_types = sorted(REQUIRED_TYPES - native)
    if missing_types:
        raise SystemExit("issue 66 native types missing: " + ", ".join(missing_types))
    missing_members = sorted(REQUIRED_MEMBERS - set(generate_api_shims.NATIVE_MEMBER_BODIES))
    if missing_members:
        raise SystemExit("issue 66 native members missing: " + ", ".join(missing_members))
    for source in (APPEARANCE, BAKER, PROVIDER, OUTFIT, ASSETS):
        if STUB_RE.search(source.read_text()):
            raise SystemExit(f"issue 66 owned Rust stubs remain in {source.name}")

    require_markers(APPEARANCE, (
        "baked_texture_hash", "wearable_bake_map", "AgentCachedTextureResponse",
        "AgentWearablesRequest", "AgentWearablesUpdate", "MAX_COF_ENTRIES",
        "RebakeAvatarTextures", "tokio::sync::Semaphore::new(5)",
        "tokio::sync::Semaphore::new(6)", "native_server_bake",
        "old_visual_parameters", "appearance_packet_contains_all_bakes",
        "legacy_wearables_packet_preserves_valid_item_asset_and_type_fields",
    ))
    require_markers(BAKER, (
        "CLASSIC_BAKE_SIZE", "EYE_BAKE_SIZE", "self.textures.len() >= 60", "head_color.tga",
        "head_skingrain.tga", "apply_parameter_mask", "output.bump",
        "native_deep_clone", '#[cfg(feature = "jpeg2000")]',
    ))
    require_markers(PROVIDER, ("ClientWeakHandle", "request_image", "native_decode_image"))
    require_markers(OUTFIT, (
        "MAX_FOLDERS", "Phase::Counting", "Phase::Copying", "Phase::Applying",
        "cleanup_folder", "compare_exchange", "native_replace_outfit",
        "handler_dispose_is_idempotent",
    ))
    require_markers(ASSETS, (
        "native_from_asset", "parameters ", "textures ",
        "wearable_parser_preserves_parameters_and_texture_indices",
    ))
    require_markers(BAKER_TESTS, ("parity-case", "image.red[0]", "image.bump[0]"))
    require_markers(VISUAL_TESTS, ("visual_catalog_matches_the_pinned_character_fixture", "672", "253"))
    require_markers(COMPAT, ("baked_texture_hash", "wearable_bake_map", "texture_provider"))
    require_markers(DOC, (
        "all 11", "downloads and six baked-texture uploads",
        "Counting`, `Copying`, `Applying`, then `Complete", "256-node",
        "big-endian", "218 normal or 251", "UpdateAvatarAppearance",
    ))
    print(
        "issue 66 audit: native bake composition, bounded texture transport, cache/server/local "
        "appearance paths, transactional initial outfit, documentation, and focused evidence are present"
    )


if __name__ == "__main__":
    main()
