//! Tests for the new T2 public API surface: typed add_events(iter) and the
//! fluent event builder (added in Task 16).

use smallvec::smallvec;
use trueskill_tt::{ConstantDrift, ConvergenceOptions, Event, History, Member, Outcome, Team};

#[test]
fn add_events_bulk_via_iter() {
    let mut h = History::builder()
        .mu(0.0)
        .sigma(2.0)
        .beta(1.0)
        .p_draw(0.0)
        .drift(ConstantDrift(0.0))
        .convergence(ConvergenceOptions {
            max_iter: 30,
            epsilon: 1e-6,
            alpha: 1.0,
        })
        .build();

    let events: Vec<Event<i64, &'static str>> = vec![
        Event {
            time: 1,
            teams: smallvec![
                Team::with_members([Member::new("a")]),
                Team::with_members([Member::new("b")]),
            ],
            outcome: Outcome::winner(0, 2),
        },
        Event {
            time: 2,
            teams: smallvec![
                Team::with_members([Member::new("b")]),
                Team::with_members([Member::new("c")]),
            ],
            outcome: Outcome::winner(0, 2),
        },
    ];

    h.add_events(events).unwrap();
    let report = h.converge().unwrap();
    assert!(report.converged);
    assert!(h.lookup(&"a").is_some());
    assert!(h.lookup(&"b").is_some());
    assert!(h.lookup(&"c").is_some());
}

#[test]
fn add_events_draw() {
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.25)
        .drift(ConstantDrift(25.0 / 300.0))
        .build();

    let events: Vec<Event<i64, &'static str>> = vec![Event {
        time: 1,
        teams: smallvec![
            Team::with_members([Member::new("alice")]),
            Team::with_members([Member::new("bob")]),
        ],
        outcome: Outcome::draw(2),
    }];
    h.add_events(events).unwrap();
    h.converge().unwrap();
}

#[test]
fn add_events_rejects_mismatched_outcome_ranks() {
    use trueskill_tt::InferenceError;
    let mut h: History = History::builder().build();
    let events: Vec<Event<i64, &'static str>> = vec![Event {
        time: 1,
        teams: smallvec![
            Team::with_members([Member::new("a")]),
            Team::with_members([Member::new("b")]),
        ],
        outcome: Outcome::ranking([0, 1, 2]), // 3 ranks but 2 teams
    }];
    let err = h.add_events(events).unwrap_err();
    assert!(matches!(err, InferenceError::MismatchedShape { .. }));
}

#[test]
fn fluent_event_builder_basic() {
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.0)
        .build();

    h.event(1)
        .team(["alice", "bob"])
        .weights([1.0, 0.7])
        .team(["carol"])
        .ranking([1, 0])
        .commit()
        .unwrap();

    let report = h.converge().unwrap();
    assert!(report.converged);
    assert!(h.lookup(&"alice").is_some());
    assert!(h.lookup(&"bob").is_some());
    assert!(h.lookup(&"carol").is_some());
}

#[test]
fn fluent_event_builder_winner_convenience() {
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.0)
        .build();

    h.event(1)
        .team(["alice"])
        .team(["bob"])
        .winner(0)
        .commit()
        .unwrap();
    h.converge().unwrap();
}

#[test]
fn fluent_event_builder_draw() {
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.25)
        .build();

    h.event(1)
        .team(["alice"])
        .team(["bob"])
        .draw()
        .commit()
        .unwrap();
    h.converge().unwrap();
}

#[test]
fn current_skill_and_learning_curve() {
    use trueskill_tt::History;
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.0)
        .build();
    h.record_winner(&"a", &"b", 1).unwrap();
    h.record_winner(&"a", &"b", 2).unwrap();
    h.converge().unwrap();

    let a = h.current_skill(&"a").unwrap();
    assert!(a.mu() > 25.0);
    let b = h.current_skill(&"b").unwrap();
    assert!(b.mu() < 25.0);

    let a_curve = h.learning_curve(&"a");
    assert_eq!(a_curve.len(), 2);
    assert_eq!(a_curve[0].0, 1);
    assert_eq!(a_curve[1].0, 2);

    let all = h.learning_curves();
    assert_eq!(all.len(), 2);
    assert!(all.contains_key("a"));
    assert!(all.contains_key("b"));
}

#[test]
fn log_evidence_total_vs_subset() {
    use trueskill_tt::{ConstantDrift, History};
    let mut h = History::builder()
        .mu(0.0)
        .sigma(6.0)
        .beta(1.0)
        .p_draw(0.0)
        .drift(ConstantDrift(0.0))
        .build();
    h.record_winner(&"a", &"b", 1).unwrap();
    h.record_winner(&"b", &"a", 2).unwrap();
    let total = h.log_evidence();
    let a_only = h.log_evidence_for(&[&"a"]);
    assert!(total.is_finite());
    assert!(a_only.is_finite());
}

#[test]
fn predict_quality_two_teams() {
    use trueskill_tt::History;
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.0)
        .build();
    h.record_winner(&"a", &"b", 1).unwrap();
    h.converge().unwrap();

    let q = h.predict_quality(&[&[&"a"], &[&"b"]]);
    assert!(q > 0.0 && q <= 1.0);
}

#[test]
fn predict_outcome_two_teams_sums_to_one() {
    use trueskill_tt::History;
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .p_draw(0.0)
        .build();
    h.record_winner(&"a", &"b", 1).unwrap();
    h.converge().unwrap();

    let p = h.predict_outcome(&[&[&"a"], &[&"b"]]);
    assert_eq!(p.len(), 2);
    assert!((p[0] + p[1] - 1.0).abs() < 1e-9);
    assert!(p[0] > p[1]);
}

#[test]
fn fluent_event_builder_scores() {
    use trueskill_tt::ConstantDrift;
    let mut h = History::builder()
        .mu(25.0)
        .sigma(25.0 / 3.0)
        .beta(25.0 / 6.0)
        .drift(ConstantDrift(0.0))
        .build();

    h.event(1)
        .team(["alice"])
        .team(["bob"])
        .scores([12.0, 4.0])
        .commit()
        .unwrap();
    h.converge().unwrap();

    let a = h.current_skill(&"alice").unwrap();
    let b = h.current_skill(&"bob").unwrap();
    assert!(a.mu() > b.mu());
}
