//! M10p3 integration test — Provenance, Explainability, Remove-by-Scope.
//!
//! Validates that every community contribution carries provenance (user, writer
//! agent, epoch), that an aggregate can be explained as the contributions that
//! compose it, and that contributions can be removed precisely by user or by
//! agent — non-globally, deterministically, with the local profile intact and
//! the removal surviving reopen. Exercises the M10 UAT: revoke `A_trusted`, then
//! remove `A_trusted`'s prior contributions.
//!
//! ROADMAP §Milestone 10 Phase 3.

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

use std::time::Duration;

use tidaldb::{
    CommunityId, RemoveScope, 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])
}

fn grant_writer(db: &TidalDb, agent: &str) {
    db.grant_capability(
        agent,
        vec![ScopePermission::read_write(ScopeClass::Community)],
        None,
    )
    .unwrap();
}

#[test]
fn contributions_carry_provenance_and_explain() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (c, item) = (CommunityId(1), EntityId::new(5));
    db.join_community(UserId(1), c, share()).unwrap();
    grant_writer(&db, "A_trusted");
    let now = Timestamp::now();

    // A direct user write + an agent-attributed write on the same item.
    db.signal_for_community("like", item, 1.0, now, UserId(1), c)
        .unwrap();
    db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, UserId(1), c)
        .unwrap();

    let provenance = db.explain_community_contributions(c, item, "like").unwrap();
    assert_eq!(
        provenance.len(),
        2,
        "two distinct contributors (direct + agent)"
    );
    // Direct write has empty writer; agent write is attributed to A_trusted.
    assert!(
        provenance
            .iter()
            .any(|p| p.user == UserId(1) && p.writer_agent.is_empty())
    );
    assert!(
        provenance
            .iter()
            .any(|p| p.user == UserId(1) && p.writer_agent == "A_trusted")
    );
}

#[test]
fn remove_by_agent_is_precise() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
    db.join_community(user, c, share()).unwrap();
    grant_writer(&db, "A_trusted");
    grant_writer(&db, "A_other");
    let now = Timestamp::now();

    db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
        .unwrap();
    db.agent_signal_for_community("A_other", "like", item, 1.0, now, user, c)
        .unwrap();
    db.signal_for_community("like", item, 1.0, now, user, c)
        .unwrap(); // direct
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        3
    );

    // Remove ONLY A_trusted's contributions.
    let receipt = db
        .remove_from_personalization(RemoveScope::CommunityAgent {
            writer_agent: "A_trusted".to_string(),
            community: c,
        })
        .unwrap();
    assert_eq!(receipt.contributions_removed, 1);

    // A_other's + the direct contribution remain (precise, non-global).
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        2
    );
    let remaining = db.explain_community_contributions(c, item, "like").unwrap();
    assert!(!remaining.iter().any(|p| p.writer_agent == "A_trusted"));
    assert!(remaining.iter().any(|p| p.writer_agent == "A_other"));
}

#[test]
fn m10_uat_revoke_and_remove_agent() {
    // The ROADMAP M10 UAT step: U revokes A_trusted community scope and removes
    // A_trusted's prior contributions from C; the local profile is untouched.
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
    db.join_community(user, c, share()).unwrap();
    let token = db
        .grant_capability(
            "A_trusted",
            vec![ScopePermission::read_write(ScopeClass::Community)],
            None,
        )
        .unwrap();
    let now = Timestamp::now();

    db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
        .unwrap();
    db.signal("like", item, 1.0, now).unwrap(); // U's own local like
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        1
    );
    let local_before = db
        .read_windowed_count(item, "like", Window::AllTime)
        .unwrap();

    // 1. Revoke A_trusted's community scope -> future writes denied immediately.
    db.revoke_capability(&token).unwrap();
    assert!(
        db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
            .is_err()
    );

    // 2. Remove A_trusted's prior contributions from C.
    db.remove_from_personalization(RemoveScope::CommunityAgent {
        writer_agent: "A_trusted".to_string(),
        community: c,
    })
    .unwrap();
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        0
    );

    // 3. U's local profile is completely intact.
    assert_eq!(
        db.read_windowed_count(item, "like", Window::AllTime)
            .unwrap(),
        local_before
    );
}

#[test]
fn remove_by_user_scope_delegates_to_purge() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (c, item) = (CommunityId(1), EntityId::new(5));
    let now = Timestamp::now();
    for u in 1..=3u64 {
        db.join_community(UserId(u), c, share()).unwrap();
        db.signal_for_community("like", item, 1.0, now, UserId(u), c)
            .unwrap();
    }
    let receipt = db
        .remove_from_personalization(RemoveScope::CommunityUser {
            user: UserId(2),
            community: c,
            epoch_range: 0..=u32::MAX,
        })
        .unwrap();
    assert_eq!(receipt.contributions_removed, 1);
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        2
    );
}

#[test]
fn remove_survives_reopen() {
    let home = tidaldb::TempTidalHome::new().unwrap();
    let (c, item, user) = (CommunityId(1), EntityId::new(5), UserId(1));
    {
        let db = TidalDb::builder()
            .with_data_dir(home.path())
            .with_schema(schema())
            .open()
            .unwrap();
        db.join_community(user, c, share()).unwrap();
        grant_writer(&db, "A_trusted");
        let now = Timestamp::now();
        db.agent_signal_for_community("A_trusted", "like", item, 1.0, now, user, c)
            .unwrap();
        db.signal_for_community("like", item, 1.0, now, user, c)
            .unwrap();
        db.remove_from_personalization(RemoveScope::CommunityAgent {
            writer_agent: "A_trusted".to_string(),
            community: c,
        })
        .unwrap();
        assert_eq!(
            db.read_community_windowed_count(c, item, "like", Window::AllTime)
                .unwrap(),
            1
        );
        db.shutdown().unwrap();
    }
    // Reopen: the remove-by-agent outcome is preserved (the agent's contribution
    // stays removed, the direct one stays).
    let db = TidalDb::builder()
        .with_data_dir(home.path())
        .with_schema(schema())
        .open()
        .unwrap();
    assert_eq!(
        db.read_community_windowed_count(c, item, "like", Window::AllTime)
            .unwrap(),
        1
    );
    let prov = db.explain_community_contributions(c, item, "like").unwrap();
    assert!(!prov.iter().any(|p| p.writer_agent == "A_trusted"));
}
