//! M10p2 integration test — Agent Capability & Scope Controls.
//!
//! Validates the ROADMAP M10 UAT agent-rights flow: a trusted agent with a
//! community-write capability can contribute, an experimental agent without it
//! is rejected, revocation takes effect immediately, and capabilities +
//! revocation history survive close/reopen.
//!
//! ROADMAP §Milestone 10 Phase 2.

#![allow(clippy::too_many_lines, clippy::unwrap_used)]

use std::time::Duration;

use tidaldb::{
    CommunityId, ScopeClass, ScopePermission, ShareIntent, SharePolicy, TidalDb, UserId,
    schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};

fn schema() -> tidaldb::schema::Schema {
    let mut b = SchemaBuilder::new();
    let _ = b
        .signal(
            "like",
            EntityKind::Item,
            DecaySpec::Exponential {
                half_life: Duration::from_secs(7 * 24 * 3600),
            },
        )
        .windows(&[Window::AllTime])
        .velocity(false)
        .add();
    b.build().unwrap()
}

fn share() -> SharePolicy {
    SharePolicy::community_share(1, [ShareIntent::Like])
}

#[test]
fn trusted_agent_writes_experimental_is_rejected() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (community, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
    db.join_community(user, community, share()).unwrap();

    // A_trusted gets community write; A_experimental gets only community read.
    db.grant_capability(
        "A_trusted",
        vec![ScopePermission::read_write(ScopeClass::Community)],
        None,
    )
    .unwrap();
    db.grant_capability(
        "A_experimental",
        vec![ScopePermission::read_only(ScopeClass::Community)],
        None,
    )
    .unwrap();

    let now = Timestamp::now();
    // Trusted agent: allowed -> contribution recorded.
    assert!(
        db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, community)
            .unwrap()
    );
    // Experimental agent: denied by capability policy -> error, nothing written.
    assert!(
        db.agent_signal_for_community("A_experimental", "like", item, 1.0, now, user, community)
            .is_err()
    );
    // Unknown agent: also denied.
    assert!(
        db.agent_signal_for_community("A_unknown", "like", item, 1.0, now, user, community)
            .is_err()
    );

    // Exactly one contribution (from A_trusted) landed.
    assert_eq!(
        db.read_community_windowed_count(community, item, "like", Window::AllTime)
            .unwrap(),
        1
    );
}

#[test]
fn revocation_takes_effect_immediately() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (community, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
    db.join_community(user, community, share()).unwrap();
    let token = db
        .grant_capability(
            "A",
            vec![ScopePermission::read_write(ScopeClass::Community)],
            None,
        )
        .unwrap();

    let now = Timestamp::now();
    assert!(db.check_capability("A", ScopeClass::Community, true));
    assert!(
        db.agent_signal_for_community("A", "like", item, 1.0, now, user, community)
            .unwrap()
    );

    // Revoke -> the very next check/write is denied (atomic, no sweeper).
    assert!(db.revoke_capability(&token).unwrap());
    assert!(!db.check_capability("A", ScopeClass::Community, true));
    assert!(
        db.agent_signal_for_community("A", "like", item, 1.0, now, user, community)
            .is_err()
    );

    // Revoking an unknown token returns false.
    assert!(!db.revoke_capability("does-not-exist").unwrap());
}

#[test]
fn ttl_expiry_denies() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    // Grant with a zero TTL -> already expired.
    db.grant_capability(
        "A",
        vec![ScopePermission::read_write(ScopeClass::Community)],
        Some(Duration::from_nanos(0)),
    )
    .unwrap();
    assert!(!db.check_capability("A", ScopeClass::Community, true));
}

#[test]
fn capabilities_and_revocations_survive_reopen() {
    let home = tidaldb::TempTidalHome::new().unwrap();
    let token_id;
    {
        let db = TidalDb::builder()
            .with_data_dir(home.path())
            .with_schema(schema())
            .open()
            .unwrap();
        db.grant_capability(
            "keep",
            vec![ScopePermission::read_write(ScopeClass::Community)],
            None,
        )
        .unwrap();
        token_id = db
            .grant_capability(
                "revoked_agent",
                vec![ScopePermission::read_write(ScopeClass::Community)],
                None,
            )
            .unwrap();
        db.revoke_capability(&token_id).unwrap();
        db.shutdown().unwrap();
    }

    let db = TidalDb::builder()
        .with_data_dir(home.path())
        .with_schema(schema())
        .open()
        .unwrap();
    // The live capability is restored and usable.
    assert!(db.check_capability("keep", ScopeClass::Community, true));
    // The revoked one stays revoked across restart.
    assert!(!db.check_capability("revoked_agent", ScopeClass::Community, true));
    // Revocation history is inspectable.
    let history = db.revocation_history();
    assert!(
        history
            .iter()
            .any(|(tid, agent, _)| tid == &token_id && agent == "revoked_agent")
    );
}
