//! M9p2 integration test — Membership Lifecycle & Stop-Forward.
//!
//! Validates the join / leave(stop-forward) / rejoin lifecycle end-to-end:
//! community contributions flow while joined, are gated the instant a member
//! leaves (without touching the local profile), and resume under a fresh epoch
//! after rejoin. Membership survives close/reopen.
//!
//! ROADMAP §Milestone 9 Phase 2.

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

use std::time::Duration;

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

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

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

#[test]
fn join_enables_eligible_contributions() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (user, community) = (UserId(1), CommunityId(10));
    let epoch = db.join_community(user, community, share_policy()).unwrap();
    assert_eq!(epoch, 0);
    assert_eq!(db.membership_epoch(user, community), Some(0));
    assert_eq!(
        db.membership_state(user, community),
        Some(MembershipState::Joined)
    );

    let now = Timestamp::now();
    // Allowed intent flows.
    assert!(
        db.signal_for_community("like", EntityId::new(5), 1.0, now, user, community)
            .unwrap()
    );
    // Disallowed intent (view) is gated by the share policy.
    assert!(
        !db.signal_for_community("view", EntityId::new(5), 1.0, now, user, community)
            .unwrap()
    );
    // Non-member is gated.
    assert!(
        !db.signal_for_community("like", EntityId::new(5), 1.0, now, UserId(99), community)
            .unwrap()
    );
}

#[test]
fn leave_stop_forward_gates_contributions_but_keeps_local_profile() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (user, community) = (UserId(1), CommunityId(10));
    db.join_community(user, community, share_policy()).unwrap();
    let now = Timestamp::now();
    let item = EntityId::new(7);

    // Before leave: community contribution flows.
    assert!(
        db.signal_for_community("like", item, 1.0, now, user, community)
            .unwrap()
    );

    // Establish a local-profile baseline on the same item.
    db.signal("like", item, 1.0, now).unwrap();
    let local_before = db
        .read_windowed_count(item, "like", Window::AllTime)
        .unwrap();
    assert_eq!(local_before, 1, "one local like recorded");

    // Leave with stop-forward.
    db.leave_community(user, community, LeaveMode::StopForward)
        .unwrap();
    assert_eq!(
        db.membership_state(user, community),
        Some(MembershipState::Left)
    );

    // After leave: community contribution is GATED (records zero).
    assert!(
        !db.signal_for_community("like", item, 1.0, now, user, community)
            .unwrap(),
        "post-leave community contribution must be gated"
    );

    // Local profile keeps updating — local-profile-intact invariant.
    db.signal("like", item, 1.0, now).unwrap();
    let local_after = db
        .read_windowed_count(item, "like", Window::AllTime)
        .unwrap();
    assert_eq!(
        local_after, 2,
        "local profile must keep updating after leave"
    );
}

#[test]
fn rejoin_opens_new_epoch_and_reenables() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (user, community) = (UserId(2), CommunityId(20));
    db.join_community(user, community, share_policy()).unwrap();
    db.leave_community(user, community, LeaveMode::StopForward)
        .unwrap();

    // Gated while left.
    let now = Timestamp::now();
    assert!(
        !db.signal_for_community("like", EntityId::new(1), 1.0, now, user, community)
            .unwrap()
    );

    // Rejoin -> epoch advances by exactly 1, gate clears, state Rejoined.
    let new_epoch = db
        .rejoin_community(user, community, share_policy())
        .unwrap();
    assert_eq!(new_epoch, 1);
    assert_eq!(db.membership_epoch(user, community), Some(1));
    assert_eq!(
        db.membership_state(user, community),
        Some(MembershipState::Rejoined)
    );

    // Contributions flow again under the new epoch.
    assert!(
        db.signal_for_community("like", EntityId::new(1), 1.0, now, user, community)
            .unwrap()
    );
}

#[test]
fn purge_leave_mode_is_rejected_until_m9p3() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let (user, community) = (UserId(3), CommunityId(30));
    db.join_community(user, community, share_policy()).unwrap();
    let err = db.leave_community(user, community, LeaveMode::Purge);
    assert!(err.is_err(), "Purge leave mode is deferred to M9p3");
}

#[test]
fn leave_without_membership_errors() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let err = db.leave_community(UserId(404), CommunityId(1), LeaveMode::StopForward);
    assert!(err.is_err());
}

#[test]
fn membership_survives_reopen_with_epoch_and_gate() {
    let home = tidaldb::TempTidalHome::new().unwrap();
    let (user, community) = (UserId(1), CommunityId(10));

    {
        let db = TidalDb::builder()
            .with_data_dir(home.path())
            .with_schema(schema())
            .open()
            .unwrap();
        db.join_community(user, community, share_policy()).unwrap();
        db.leave_community(user, community, LeaveMode::StopForward)
            .unwrap();
        db.rejoin_community(user, community, share_policy())
            .unwrap(); // epoch -> 1
        db.shutdown().unwrap();
    }

    // Reopen: registry rebuilt from Tag::Membership.
    let db = TidalDb::builder()
        .with_data_dir(home.path())
        .with_schema(schema())
        .open()
        .unwrap();
    assert_eq!(
        db.membership_epoch(user, community),
        Some(1),
        "epoch must survive reopen"
    );
    assert_eq!(
        db.membership_state(user, community),
        Some(MembershipState::Rejoined)
    );
    // Gate was cleared on rejoin, so contributions flow after reopen.
    let now = Timestamp::now();
    assert!(
        db.signal_for_community("like", EntityId::new(1), 1.0, now, user, community)
            .unwrap()
    );
}
