use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};

const ALLOWED_DEPENDENCIES: [&str; 22] = [
    "async-trait",
    "base64",
    "crossterm",
    "directories",
    "libremetaverse",
    "libremetaverse-imaging",
    "libremetaverse-rendering-simple",
    "metacrate-game-loop",
    "metacrate-lsl-tools",
    "metacrate-rendering-wgpu",
    "mentra",
    "jpeg-encoder",
    "libremetaverse-types",
    "rustls",
    "serde",
    "serde_json",
    "serde_yaml_ng",
    "sha2",
    "tokio",
    "tokio-rustls",
    "url",
    "unicode-width",
];

#[test]
fn package_has_only_reviewed_rust_dependencies_and_no_build_script() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    assert!(
        !root.join("build.rs").exists(),
        "agent must not have a build script"
    );
    let manifest = fs::read_to_string(root.join("Cargo.toml")).expect("read package manifest");
    let dependency_section = manifest
        .split("[dependencies]")
        .nth(1)
        .expect("dependency section")
        .split("\n[")
        .next()
        .expect("end of dependency section");
    let observed = dependency_section
        .lines()
        .filter_map(|line| line.split_once('=').map(|(name, _)| name.trim()))
        .filter(|name| !name.is_empty())
        .collect::<BTreeSet<_>>();
    assert_eq!(
        observed,
        ALLOWED_DEPENDENCIES.into_iter().collect(),
        "every direct dependency needs explicit policy review"
    );
}

#[test]
fn runtime_source_has_no_subprocess_or_native_abi_escape_hatch() {
    let source = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
    let mut files = Vec::with_capacity(38);
    collect_rust_files(&source, &mut files);
    assert!(
        files.len() <= 38,
        "source-file count needs a reviewed bound update"
    );
    for path in files {
        let text = fs::read_to_string(&path).expect("read runtime source");
        for forbidden in [
            "std::process::Command",
            "tokio::process",
            "Command::new(",
            "extern \"C\"",
            "#[link(",
            "unsafe fn ",
            "unsafe impl ",
        ] {
            assert!(
                !text.contains(forbidden),
                "{} contains forbidden runtime boundary {forbidden:?}",
                path.display()
            );
        }
        assert!(
            !text.lines().any(|line| {
                let line = line.trim_start();
                line.starts_with("unsafe {") || line.contains("= unsafe {")
            }),
            "{} contains an unsafe block",
            path.display()
        );
    }
}

#[test]
fn vision_path_selects_only_reviewed_rust_renderers_and_codec() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let manifest = fs::read_to_string(root.join("Cargo.toml")).expect("agent manifest");
    assert!(manifest.contains("default-features = false, features = [\"rust-j2k\"]"));
    for forbidden in ["imaging-skia", "openjpeg", "skia-safe"] {
        assert!(!manifest.contains(forbidden), "vision enables {forbidden}");
    }
    let renderer = fs::read_to_string(root.join("../libremetaverse-rendering-simple/Cargo.toml"))
        .expect("simple renderer manifest");
    assert!(!renderer.contains("imaging-skia"));
    assert!(!renderer.contains("openjpeg"));
    assert!(manifest.contains("metacrate-rendering-wgpu ="));
    let renderer = fs::read_to_string(root.join("../metacrate-rendering-wgpu/Cargo.toml"))
        .expect("wgpu renderer manifest");
    assert!(renderer.contains("bevy = { version = \"0.19.1\", default-features = false"));
    assert!(renderer.contains("wgpu = [\"bevy-engine\"]"));
}

#[test]
fn compatibility_crates_never_depend_on_or_reexport_metacrate_crates() {
    let workspace = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .and_then(Path::parent)
        .expect("workspace")
        .to_path_buf();
    for entry in fs::read_dir(workspace.join("crates")).expect("crates") {
        let path = entry.expect("crate entry").path();
        let Some(name) = path.file_name().and_then(|value| value.to_str()) else {
            continue;
        };
        if !name.starts_with("libremetaverse") || !path.is_dir() {
            continue;
        }
        let manifest = fs::read_to_string(path.join("Cargo.toml")).expect("compat manifest");
        assert!(
            !manifest.lines().any(|line| {
                let line = line.trim_start();
                line.starts_with("metacrate-") || line.contains("../metacrate-")
            }),
            "{name} must not depend on metacrate crates"
        );
        let lib = path.join("src/lib.rs");
        if lib.exists() {
            let source = fs::read_to_string(lib).expect("compat lib.rs");
            assert!(
                !source.lines().any(|line| {
                    let line = line.trim_start();
                    (line.starts_with("pub use") || line.starts_with("pub mod"))
                        && line.contains("metacrate")
                }),
                "{name} must not reexport metacrate crates"
            );
        }
    }
}

#[test]
fn world_backend_requires_the_opaque_policy_authorization() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let backend = fs::read_to_string(root.join("src/backend.rs")).expect("backend source");
    assert!(backend.contains("action: AuthorizedAction"));
    assert!(!backend.contains("call: ProposedToolCall"));
    assert!(!backend.contains("decision: PolicyDecision"));
}

#[test]
fn live_session_adapter_reuses_native_lifecycle_and_messaging_managers() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let backend = fs::read_to_string(root.join("src/backend.rs")).expect("backend source");
    for required in [
        ".native_default_login_params(",
        ".native_login(",
        ".native_subscribe_disconnected(",
        ".native_subscribe_event_queue_running(",
        ".native_logout_async(",
        ".subscribe_chat_from_simulator(",
        ".subscribe_im(",
        ".chat(",
        ".instant_message_with_uuid_string(",
        ".parcels",
        ".environment()",
        ".inventory()",
        ".store()",
        ".native_subscribe_sim_changed(",
    ] {
        assert!(
            backend.contains(required),
            "missing native lifecycle {required}"
        );
    }
    let world = fs::read_to_string(root.join("../metacrate-grid-world/src/lib.rs"))
        .expect("central world source");
    for required in [".objects_avatars", ".objects_primitives", ".terrain"] {
        assert!(
            world.contains(required),
            "missing centralized native world cache {required}"
        );
    }
    assert!(backend.contains("metacrate_game_loop::WorldReader"));
    assert!(!backend.contains("reqwest"));
}

#[test]
fn embodied_adapter_uses_only_reviewed_high_level_native_movement() {
    let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let backend = fs::read_to_string(root.join("src/backend.rs")).expect("backend source");
    for required in [
        ".movement\n                .turn_toward(",
        ".auto_pilot_local(",
        ".auto_pilot_cancel()",
        ".aabb_min",
        ".aabb_max",
        ".sit()",
        ".stand()",
    ] {
        assert!(
            backend.contains(required),
            "missing native embodiment mapping {required}"
        );
    }
    let behavior = fs::read_to_string(root.join("src/behavior.rs")).expect("behavior source");
    for forbidden in [
        "Teleport(",
        "TouchObject(",
        "set_agent_controls(",
        "FollowAvatar(",
    ] {
        assert!(
            !behavior.contains(forbidden),
            "model-facing behavior contains forbidden primitive {forbidden}"
        );
    }
}

fn collect_rust_files(directory: &Path, output: &mut Vec<PathBuf>) {
    for entry in fs::read_dir(directory).expect("read source directory") {
        let path = entry.expect("source entry").path();
        if path.is_dir() {
            collect_rust_files(&path, output);
        } else if path.extension().is_some_and(|extension| extension == "rs") {
            output.push(path);
        }
    }
}
