//! M9p1 integration test — Signal Scope & Share Contract.
//!
//! Validates the public `signal_scoped` API end-to-end through a real WAL and
//! the load-bearing M9 invariant: community/session/agent-scoped writes are
//! durably logged but never perturb the *local* profile.
//!
//! ROADMAP §Milestone 9 Phase 1 acceptance:
//! - Default behavior is local-only; sharing is explicit opt-in.
//! - The local profile remains intact across scoped writes.
//! - Share eligibility is decided by `SharePolicy` (per-intent).

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

use std::{collections::BTreeSet, time::Duration};

use tidaldb::{
    CommunityId, ShareIntent, ShareMode, SharePolicy, SignalScope, TidalDb,
    schema::{DecaySpec, EntityId, EntityKind, SchemaBuilder, Timestamp, Window},
};

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

#[test]
fn default_signal_is_local() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let id = EntityId::new(1);
    db.signal("view", id, 1.0, Timestamp::now()).unwrap();
    // A plain `signal` is local — it shows up in the local profile.
    assert!(
        db.read_decay_score(id, "view", 0)
            .unwrap()
            .is_some_and(|s| s > 0.0)
    );
}

#[test]
fn community_scope_does_not_touch_local_profile() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let id = EntityId::new(2);

    // A community-scoped write is accepted and durably logged...
    db.signal_scoped(
        "view",
        id,
        1.0,
        Timestamp::now(),
        SignalScope::Community(CommunityId(1)),
        1,
    )
    .unwrap();

    // ...but the local profile is untouched (local-profile-intact invariant).
    assert!(
        db.read_decay_score(id, "view", 0).unwrap().is_none(),
        "community write must not create a local aggregate"
    );
    assert_eq!(
        db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
        0
    );
}

#[test]
fn local_and_community_writes_are_independent() {
    let db = TidalDb::builder()
        .ephemeral()
        .with_schema(schema())
        .open()
        .unwrap();
    let id = EntityId::new(3);
    let now = Timestamp::now();

    // Interleave a local and a community write on the same entity.
    db.signal_scoped("view", id, 1.0, now, SignalScope::Local, 0)
        .unwrap();
    db.signal_scoped(
        "view",
        id,
        1.0,
        now,
        SignalScope::Community(CommunityId(9)),
        1,
    )
    .unwrap();

    // Only the local write affects the local windowed count.
    assert_eq!(
        db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
        1,
        "exactly one (the local) write must count toward the local profile"
    );
}

#[test]
fn share_policy_decides_per_intent_eligibility() {
    // The SharePolicy is the per-intent gate that a community-sync layer (M9p2)
    // consults before writing a community-scoped signal. Verify its contract
    // directly: `low_quality` is allowed to ship, `view` is not.
    let policy = SharePolicy {
        version: 1,
        allowed_intents: BTreeSet::from([ShareIntent::LowQuality]),
        mode: ShareMode::CommunityShare,
    };
    let scope = SignalScope::Community(CommunityId(1));

    assert!(policy.is_signal_share_eligible(scope, "low_quality"));
    assert!(!policy.is_signal_share_eligible(scope, "view"));
    // Local scope is never eligible, even for an allowed intent.
    assert!(!policy.is_signal_share_eligible(SignalScope::Local, "low_quality"));
}

#[test]
fn scoped_writes_survive_reopen_without_polluting_local() {
    // Durability: a community write is in the WAL and replays on reopen, but
    // still must not appear in the recovered local profile.
    let home = tidaldb::TempTidalHome::new().unwrap();
    let id = EntityId::new(42);
    let now = Timestamp::now();

    {
        let db = TidalDb::builder()
            .with_data_dir(home.path())
            .with_schema(schema())
            .open()
            .unwrap();
        db.signal_scoped("view", id, 1.0, now, SignalScope::Local, 0)
            .unwrap();
        db.signal_scoped(
            "view",
            id,
            1.0,
            now,
            SignalScope::Community(CommunityId(1)),
            1,
        )
        .unwrap();
        db.shutdown().unwrap();
    }

    // Reopen and replay the WAL.
    let db = TidalDb::builder()
        .with_data_dir(home.path())
        .with_schema(schema())
        .open()
        .unwrap();
    // Exactly the one local write is reflected in the recovered local profile.
    assert_eq!(
        db.read_windowed_count(id, "view", Window::AllTime).unwrap(),
        1,
        "after replay, only the local write should affect the local profile"
    );
}
