use libremetaverse_types::{UUID, compat::CancellationToken};
use metacrate_grid_agent::testing::{
    FakeGrid, FakeLlmStep, GridScriptStep, HarnessMetrics, HarnessRandom, HarnessScript,
    adversarial_corpus,
};
use metacrate_grid_agent::{
    BehaviorRandom, BuildGrid, BuildPrim, BuildShape, GridSessionBackend, LandmarkGrid,
    ReconnectPolicy, RoamingRandom, ScriptInventory, SessionState, SessionSupervisor,
};
use std::collections::BTreeSet;
use std::sync::Arc;
use std::time::Duration;

fn uuid(value: u32) -> UUID {
    UUID::new_with_string(format!("00000000-0000-4000-8000-{value:012}")).expect("fixture UUID")
}

async fn settle() {
    for _ in 0..32 {
        tokio::task::yield_now().await;
    }
}

#[tokio::test(start_paused = true)]
async fn scripted_lifecycle_reconnects_with_generation_fencing_and_no_resources() {
    let grid = FakeGrid::scripted([
        GridScriptStep::Ready,
        GridScriptStep::MaintenanceDisconnect,
        GridScriptStep::Ready,
        GridScriptStep::Hold,
    ]);
    let backend: Arc<dyn GridSessionBackend> = Arc::new(grid.clone());
    let mut handle = SessionSupervisor::new(
        backend,
        ReconnectPolicy {
            jitter_basis_points: 0,
            instance_seed: 73,
            ..ReconnectPolicy::default()
        },
        16,
        64,
    )
    .expect("supervisor")
    .start();
    settle().await;
    assert_eq!(handle.state(), SessionState::Backoff);
    let stale = handle.status().generation;
    tokio::time::advance(Duration::from_secs(1)).await;
    settle().await;
    assert_eq!(handle.state(), SessionState::Online);
    assert!(handle.status().generation > stale);
    assert!(!handle.accepts_result(stale));
    handle.shutdown().await.expect("shutdown");
    assert_eq!(grid.active_sessions(), 0);
    assert_eq!(grid.max_sessions(), 1);
    assert!(grid.reproduction(73).contains("seed=73"));
    let operations = grid
        .evidence()
        .into_iter()
        .map(|event| event.operation)
        .collect::<BTreeSet<_>>();
    assert!(operations.contains("grid.login"));
    assert!(operations.contains("grid.logout"));
    assert!(operations.contains("audit.flush"));
}

#[tokio::test]
async fn every_mutating_fake_boundary_emits_hashed_protocol_evidence() {
    let grid = FakeGrid::scripted([]);
    let token = CancellationToken::default();
    let prim = BuildPrim {
        id: "root".into(),
        parent: None,
        shape: BuildShape::Box,
        position_millimeters: [1_000, 2_000, 3_000],
        scale_millimeters: [500, 500, 500],
        rotation_degrees: [0.0; 3],
        color_rgba: [1.0; 4],
        material: "wood".into(),
        texture_inventory_id: None,
        name: "fixture".into(),
        description: "fixture".into(),
        script: Some("default { state_entry() {} }".into()),
    };
    BuildGrid::validate_land(&grid, "region", &[prim.position_millimeters], token.clone())
        .await
        .unwrap();
    let receipt = BuildGrid::create_prim(&grid, "tx-1", &prim, token.clone())
        .await
        .unwrap();
    BuildGrid::configure_prim(&grid, &receipt, &prim, token.clone())
        .await
        .unwrap();
    BuildGrid::insert_script(
        &grid,
        &receipt,
        prim.script.as_deref().unwrap(),
        token.clone(),
    )
    .await
    .unwrap();
    BuildGrid::confirm_prim(&grid, &receipt, token.clone())
        .await
        .unwrap();
    BuildGrid::delete_owned_prim(&grid, &receipt, token.clone())
        .await
        .unwrap();
    LandmarkGrid::accept_offer(&grid, "offer-1", token.clone())
        .await
        .unwrap();
    assert!(
        LandmarkGrid::verify_landmark(&grid, uuid(1), uuid(2), 9, token.clone())
            .await
            .unwrap()
    );
    assert!(
        LandmarkGrid::teleport_landmark(&grid, uuid(2), token.clone())
            .await
            .unwrap()
    );
    let inventory = ScriptInventory::create_full_permission(
        &grid,
        metacrate_grid_agent::GeneratedScript {
            name: "safe script".into(),
            description: "generated".into(),
            source: "default { state_entry() {} }".into(),
        },
        token.clone(),
    )
    .await
    .unwrap();
    ScriptInventory::give_to(&grid, inventory, uuid(3), token)
        .await
        .unwrap();

    let evidence = grid.evidence();
    let operations = evidence
        .iter()
        .map(|event| event.operation.as_str())
        .collect::<BTreeSet<_>>();
    for required in [
        "build.validate_land",
        "build.create",
        "build.configure",
        "build.insert_script",
        "build.confirm",
        "build.delete",
        "landmark.accept",
        "landmark.verify",
        "landmark.teleport",
        "inventory.create_script",
        "inventory.give_script",
    ] {
        assert!(
            operations.contains(required),
            "missing protocol evidence {required}"
        );
    }
    assert!(
        evidence
            .iter()
            .all(|event| event.payload_sha256.len() == 64)
    );
    assert!(!format!("{evidence:?}").contains("state_entry"));
}

#[test]
#[allow(clippy::too_many_lines)]
fn corpus_randomness_scripts_and_load_metrics_are_reproducible_and_redacted() {
    let corpus = adversarial_corpus();
    assert_eq!(corpus.len(), 17);
    let fields = corpus
        .iter()
        .map(|case| case.field)
        .collect::<BTreeSet<_>>();
    for required in [
        "public_chat",
        "instant_message",
        "object.name",
        "avatar.name",
        "approval_id",
        "generation",
        "journal",
        "all_logs",
    ] {
        assert!(fields.contains(required));
    }
    let debug = format!("{corpus:?}");
    assert!(!debug.contains("METACRATE_SECRET_CANARY_9f31"));
    assert!(!debug.contains("ignore policy"));
    let first = HarnessRandom::new(99);
    let second = HarnessRandom::new(99);
    assert_eq!(
        BehaviorRandom::next_u64(&first),
        BehaviorRandom::next_u64(&second)
    );
    assert_eq!(
        RoamingRandom::interval_seconds(&first, 10, 20),
        RoamingRandom::interval_seconds(&second, 10, 20)
    );
    let script = HarnessScript {
        seed: 99,
        grid: vec![
            GridScriptStep::PublicChat {
                avatar_id: uuid(1).to_string(),
                text: "hello".into(),
            },
            GridScriptStep::InstantMessage {
                avatar_id: uuid(2).to_string(),
                text: "private".into(),
            },
            GridScriptStep::InventoryOffer {
                avatar_id: uuid(2).to_string(),
                inventory_id: uuid(3).to_string(),
                kind: "landmark".into(),
            },
            GridScriptStep::AvatarUpdate {
                avatar_id: uuid(1).to_string(),
                position: [1, 2, 3],
            },
            GridScriptStep::ObjectUpdate {
                object_id: uuid(4).to_string(),
                local_id: 7,
                position: [4, 5, 6],
            },
            GridScriptStep::MovementReceipt {
                operation: "walk".into(),
            },
            GridScriptStep::TeleportReceipt {
                region_id: uuid(5).to_string(),
            },
            GridScriptStep::ObjectCreated {
                transaction_id: "tx".into(),
                object_id: uuid(6).to_string(),
            },
            GridScriptStep::CapabilityReply {
                capability: "InventoryAPIv3".into(),
                status: 200,
                body: "SECRET_CANARY".into(),
            },
            GridScriptStep::DelayTicks { ticks: 2 },
            GridScriptStep::Duplicate {
                correlation_id: "duplicate".into(),
            },
            GridScriptStep::MalformedPacket,
            GridScriptStep::PartialFailure {
                operation: "build.configure".into(),
            },
        ],
        llm: vec![
            FakeLlmStep::Text { body: "ok".into() },
            FakeLlmStep::ToolCalls {
                body: serde_json::json!({"choices":[]}),
            },
            FakeLlmStep::Malformed,
            FakeLlmStep::Oversized { bytes: 4096 },
            FakeLlmStep::RateLimited,
            FakeLlmStep::SlowStream { chunks: 3 },
            FakeLlmStep::Disconnect,
            FakeLlmStep::MultimodalAccepted {
                body: "seen".into(),
            },
            FakeLlmStep::MultimodalRejected,
        ],
    };
    let script_debug = format!("{script:?}");
    assert!(script_debug.contains("script_sha256"));
    assert!(!script_debug.contains("SECRET_CANARY"));
    let mut metrics = HarnessMetrics::default();
    metrics.observe_queue(8);
    metrics.observe_queue(3);
    metrics.observe_memory(4096);
    metrics.observe_response_latency(7);
    metrics.observe_control_latency(3);
    metrics.record_dropped_events(2);
    assert_eq!(metrics.queue_depth_high_water, 8);
    assert_eq!(metrics.memory_bytes_high_water, 4096);
    assert_eq!(metrics.response_latency_ticks, 7);
    assert_eq!(metrics.control_latency_ticks, 3);
    assert_eq!(metrics.dropped_events, 2);
    metrics.assert_drained().unwrap();
}
