#!/usr/bin/env python3
"""Audit issue 80's native RLV callback and service orchestration boundary."""

from __future__ import annotations

import json
import re
from pathlib import Path

import generate_api_shims
import generate_surface


ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "crates" / "libremetaverse-rlv" / "src" / "service.rs"
STATE = ROOT / "crates" / "libremetaverse-rlv" / "src" / "state.rs"
GENERATED = ROOT / "crates" / "libremetaverse-rlv" / "src" / "generated.rs"
SUPPORT = ROOT / "tests" / "compat" / "src" / "rlv_support.rs"
QUERY_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_query_basics_semantics.rs"
ACTION_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_attach_commands_semantics.rs"
NOTIFY_TESTS = ROOT / "tests" / "compat" / "tests" / "rlv_notify_semantics.rs"
DOC = ROOT / "crates" / "libremetaverse-rlv" / "README.md"
WORKFLOW = ROOT / ".gitea" / "workflows" / "ci.yml"
CATALOG = ROOT / "api" / "public-api.json"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")

TYPES = {
    "T:LibreMetaverse.RLV.AttachmentRequest": "crate::service::AttachmentRequest",
    "T:LibreMetaverse.RLV.RlvActionCallbacksDefault":
        "crate::service::RlvActionCallbacksDefault",
    "T:LibreMetaverse.RLV.RlvCallbacksDefault": "crate::service::RlvCallbacksDefault",
    "T:LibreMetaverse.RLV.RlvCommandProcessor": "crate::service::RlvCommandProcessor",
    "T:LibreMetaverse.RLV.RlvService": "crate::service::RlvService",
}


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 catalog_members() -> set[str]:
    catalog = json.loads(CATALOG.read_text())
    assembly = next(
        value for value in catalog["assemblies"]
        if value["identity"]["name"] == "LibreMetaverse.RLV"
    )
    return {
        member["doc_id"]
        for api_type in assembly["types"]
        if api_type["doc_id"] in TYPES
        for member in api_type["members"]
    }


def generated_type_block(text: str, doc_id: str) -> str:
    marker = f"/// C# type: `{doc_id}`."
    start = text.find(marker)
    if start < 0:
        raise SystemExit(f"generated declaration is missing for {doc_id}")
    next_type = text.find("/// C# type:", start + len(marker))
    return text[start:] if next_type < 0 else text[start:next_type]


def main() -> None:
    for api_type, declaration in TYPES.items():
        if generate_api_shims.NATIVE_TYPES.get(api_type) != declaration:
            raise SystemExit(f"issue 80 native type mapping is missing for {api_type}")
    members = catalog_members()
    if len(members) != 53:
        raise SystemExit(f"issue 80 expected 53 mapped members, found {len(members)}")

    source = SOURCE.read_text()
    if STUB_RE.search(source):
        raise SystemExit("issue 80 owned Rust stubs remain in service.rs")
    if re.search(r"\b(?:Mutex|RwLock|read_lock|write_lock)\b", source):
        raise SystemExit("service.rs must not acquire internal locks around host callbacks")
    generated = GENERATED.read_text()
    for api_type in TYPES:
        if STUB_RE.search(generated_type_block(generated, api_type)):
            raise SystemExit(f"issue 80 owned generated stubs remain for {api_type}")
    if "pub trait IRlvActionCallbacks: Send + Sync" not in generated:
        raise SystemExit("action callbacks are not thread-safe")
    if "pub trait IRlvQueryCallbacks: Send + Sync" not in generated:
        raise SystemExit("query callbacks are not thread-safe")

    catalog = json.loads((ROOT / "tests" / "upstream-tests.json").read_text())
    rlv_cases = [
        case for case in catalog["tests"]
        if case.get("rust_file", "").startswith("tests/compat/tests/rlv_")
    ]
    if len(rlv_cases) != 616 or len({case["rust_file"] for case in rlv_cases}) != 24:
        raise SystemExit("issue 80 RLV parity catalog is not the complete 616-case slice")
    markers = generate_surface.scan_parity_markers(ROOT)
    evidence_keys = ("status", "rust_file", "rust_line", "rust_test", "rust_body_sha256")
    for case in rlv_cases:
        marker = markers.get(case["id"], [])
        if len(marker) != 1 or any(marker[0][key] != case[key] for key in evidence_keys):
            raise SystemExit(f"issue 80 parity metadata is stale for {case['id']}")

    require_markers(SOURCE, (
        "MAX_RLV_MESSAGE_BYTES", "MAX_RLV_COMMANDS", "pub struct AttachmentRequest",
        "pub struct RlvActionCallbacksDefault", "pub struct RlvCallbacksDefault",
        "pub struct RlvCommandProcessor", "pub struct RlvService",
        "process_instant_message", "process_message", "process_restriction",
        "send_restriction_notification", "send_notification", "refresh_inventory",
        "report_inventory_offer_accepted", "report_item_attached",
        "report_send_public_message", "report_sit", "throw_if_cancellation_requested",
        "default_callbacks_observe_cancellation_without_side_effects",
        "attachment_request_hash_is_stable_and_value_based",
    ))
    require_markers(STATE, (
        "set_action_callbacks", "notify_removed_restriction",
        "let removed = {", "let callbacks = read_lock", ".send_reply(",
    ))
    require_markers(SUPPORT, (
        "struct RecordingActions", "struct RecordingQueries", "struct RlvHarness",
        "inventory_map.clone()", "RlvService::new",
    ))
    require_markers(QUERY_TESTS, (
        "manual_blacklist_all", "manual_version", "get_environment_known_setting",
        "get_status_all_senders", "get_current_sit_id",
    ))
    require_markers(ACTION_TESTS, (
        "attach_this_by_id", "attach_all_this_recursive",
        "attach_over_or_replace_uses_position_from_folder_name",
    ))
    require_markers(NOTIFY_TESTS, (
        "notify_every_restriction", "notify_multiple_channels_filtered",
        "notify_inventory_offer", "notify_sit_and_stand_legal",
    ))
    require_markers(DOC, (
        "Callback and service contract", "Send + Sync",
        "after releasing every manager lock", "completed inventory and agent",
        "616", "ubuntu-latest",
    ))
    require_markers(WORKFLOW, ("required-gate",))
    print(
        "issue 80 audit: native callbacks, typed actions and queries, bounded service "
        "dispatch, inventory/agent adapters, cancellation, post-lock notifications, "
        "recording-fake workflows, compatibility evidence, and docs are present"
    )


if __name__ == "__main__":
    main()
