#!/usr/bin/env python3
"""Audit issue 78's pure native RLV protocol ownership and evidence 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-rlv" / "src" / "protocol.rs"
GENERATED = ROOT / "crates" / "libremetaverse-rlv" / "src" / "generated.rs"
TESTS = ROOT / "crates" / "libremetaverse-rlv" / "tests" / "protocol_parsing.rs"
COMPAT = ROOT / "tests" / "compat" / "tests" / "rlv_common_semantics.rs"
DOC = ROOT / "crates" / "libremetaverse-rlv" / "README.md"
WORKFLOW = ROOT / ".gitea" / "workflows" / "rust-workspace.yml"
STUB_RE = re.compile(r"\b(?:not_implemented|unimplemented_api)\b|\b(?:todo|unimplemented)!\s*\(")

TYPES = {
    "T:LibreMetaverse.RLV.RlvCommon": "crate::protocol::RlvCommon",
    "T:LibreMetaverse.RLV.RlvRestriction": "crate::protocol::RlvRestriction",
}
MEMBERS = {
    "M:LibreMetaverse.RLV.RlvCommon.TryGetAttachmentPointFromItemName(System.String,System.Nullable{LibreMetaverse.RLV.RlvAttachmentPoint}@)",
    "M:LibreMetaverse.RLV.RlvRestriction.#ctor(LibreMetaverse.RLV.RlvRestrictionType,System.Guid,System.String,System.Collections.Generic.ICollection{System.Object})",
    "M:LibreMetaverse.RLV.RlvRestriction.Equals(System.Object)",
    "M:LibreMetaverse.RLV.RlvRestriction.GetHashCode",
    "M:LibreMetaverse.RLV.RlvRestriction.ToString",
    "P:LibreMetaverse.RLV.RlvRestriction.Args",
    "P:LibreMetaverse.RLV.RlvRestriction.Behavior",
    "P:LibreMetaverse.RLV.RlvRestriction.IsException",
    "P:LibreMetaverse.RLV.RlvRestriction.OriginalBehavior",
    "P:LibreMetaverse.RLV.RlvRestriction.Sender",
    "P:LibreMetaverse.RLV.RlvRestriction.SenderName",
}


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 generated_type_block(text: str, rust_name: str) -> str:
    marker = f"pub use crate::protocol::{rust_name};"
    start = text.find(marker)
    if start < 0:
        raise SystemExit(f"generated declaration for {rust_name} is missing")
    next_type = text.find("\n/// 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_DECLARATIONS.get(api_type) != declaration:
            raise SystemExit(f"issue 78 native declaration is missing for {api_type}")
    missing = sorted(MEMBERS - set(generate_api_shims.NATIVE_MEMBER_BODIES))
    if missing:
        raise SystemExit("issue 78 native members missing: " + ", ".join(missing))
    source = SOURCE.read_text()
    if STUB_RE.search(source):
        raise SystemExit("issue 78 owned Rust stubs remain in protocol.rs")
    table = source[source.index("macro_rules! restriction_table") : source.index("macro_rules! make_restriction_lookup")]
    if len(re.findall(r'"[^"]+"\s*=>\s*[A-Za-z0-9_]+', table)) != 119:
        raise SystemExit("issue 78 restriction table does not contain exactly 119 names")
    generated = GENERATED.read_text()
    for rust_name in ("RlvCommon", "RlvRestriction"):
        if STUB_RE.search(generated_type_block(generated, rust_name)):
            raise SystemExit(f"issue 78 owned generated stubs remain for {rust_name}")

    require_markers(SOURCE, (
        "MAX_RLV_MESSAGE_BYTES", "MAX_RLV_COMMANDS", "pub struct RlvSourceSpan",
        "pub enum RlvParseErrorKind", "pub enum RlvDirective", "pub enum RlvAction",
        "pub enum RlvQuery", "pub enum RlvValue", "pub fn parse_message",
        "parse_restriction_values", "RLV_RESTRICTION_NAMES",
        '"root" | "avatar center" => P::AvatarCenter', "native_try_get_attachment_point",
        "pub struct RlvRestriction", "real_restriction", "is_exception",
    ))
    require_markers(TESTS, (
        "message_preserves_original_casing_options_and_byte_locations",
        "query_variants_preserve_filters_separators_paths_and_aliases",
        "every_pinned_restriction_name_round_trips_without_normalization",
        "every_pinned_restriction_has_at_least_one_typed_valid_option_form",
        "deterministic_single_byte_mutations_are_bounded_and_never_panic",
        "command_count_and_message_size_are_bounded",
        "mapped_restriction_preserves_alias_exception_equality_and_snapshot_args",
    ))
    require_markers(COMPAT, (
        "attachment_point_uses_last_known_tag",
        "attachment_point_avatar_center",
        "attachment_point_root_alias",
        "attachment_point_rejects_unknown_tags",
    ))
    require_markers(DOC, (
        "side-effect-free", "119 behavior", "56 pinned", "half-open byte span",
        "64 KiB", "128 comma-separated", "does not wait", "ubuntu-latest",
    ))
    require_markers(WORKFLOW, ("python3 tools/check_milestone_10_issue_78.py",))
    print(
        "issue 78 audit: bounded pure RLV parsing, typed commands/queries/restrictions, "
        "exact aliases, positioned errors, mapped values, mutation evidence, and docs are present"
    )


if __name__ == "__main__":
    main()
