//! Unit tests for CardStatsCallback and GameCardTracker.

use cardgame::stats::{CardPlayStats, CardStatsCollector, GameCardTracker};
use cardgame::types::{CardId, PlayerId};

// ── GameCardTracker ──────────────────────────────────────────────────────────

#[test]
fn test_tracker_new_is_empty() {
    let tracker = GameCardTracker::new();
    assert!(tracker.p1_plays.is_empty());
    assert!(tracker.p2_plays.is_empty());
    assert!(tracker.all_cards().is_empty());
}

#[test]
fn test_tracker_record_play() {
    let mut tracker = GameCardTracker::new();
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 3);

    assert_eq!(tracker.p1_plays.len(), 1);
    assert_eq!(tracker.p2_plays.len(), 0);
    assert!(tracker.p1_cards.contains(&CardId(1000)));
    assert_eq!(tracker.p1_plays[&CardId(1000)], 1);
}

#[test]
fn test_tracker_record_multiple_plays() {
    let mut tracker = GameCardTracker::new();
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1001), 5);
    tracker.record_play(PlayerId::PLAYER_TWO, CardId(2000), 3);

    assert_eq!(tracker.p1_plays.len(), 2);
    assert_eq!(tracker.p2_plays.len(), 1);
    assert_eq!(tracker.all_cards().len(), 3);
}

#[test]
fn test_tracker_turn_buckets() {
    let mut tracker = GameCardTracker::new();

    // Early game (turn 1-5)
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);
    // Mid game (turn 6-15)
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1001), 10);
    // Late game (turn 16+)
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1002), 20);

    assert_eq!(tracker.p1_turn_buckets.len(), 3);

    // Verify bucket assignments
    let (early, mid, late) = tracker.p1_turn_buckets[&CardId(1000)];
    assert_eq!((early, mid, late), (1, 0, 0));

    let (early, mid, late) = tracker.p1_turn_buckets[&CardId(1001)];
    assert_eq!((early, mid, late), (0, 1, 0));

    let (early, mid, late) = tracker.p1_turn_buckets[&CardId(1002)];
    assert_eq!((early, mid, late), (0, 0, 1));
}

#[test]
fn test_tracker_reset() {
    let mut tracker = GameCardTracker::new();
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);
    tracker.record_play(PlayerId::PLAYER_TWO, CardId(2000), 3);

    tracker.reset();

    assert!(tracker.p1_plays.is_empty());
    assert!(tracker.p2_plays.is_empty());
    assert!(tracker.all_cards().is_empty());
}

// ── CardPlayStats ────────────────────────────────────────────────────────────

#[test]
fn test_card_play_stats_win_rate() {
    let stats = CardPlayStats {
        play_count: 10,
        games_played_in: 8,
        wins_when_played: 6,
        early_plays: 3,
        mid_plays: 5,
        late_plays: 2,
    };

    assert!((stats.win_rate() - 0.75).abs() < 0.001);
}

#[test]
fn test_card_play_stats_win_rate_no_games() {
    let stats = CardPlayStats::default();
    assert_eq!(stats.win_rate(), 0.0);
}

#[test]
fn test_card_play_stats_win_delta() {
    let stats = CardPlayStats {
        play_count: 10,
        games_played_in: 8,
        wins_when_played: 6,
        ..Default::default()
    };

    assert!((stats.win_delta(0.5) - 0.25).abs() < 0.001);
}

#[test]
fn test_card_play_stats_impact_score() {
    let stats = CardPlayStats {
        play_count: 20,
        games_played_in: 10,
        wins_when_played: 8,
        ..Default::default()
    };

    let impact = stats.impact_score(0.5, 100);
    assert!(impact > 0.0, "Card with 80% win rate should have positive impact");
}

#[test]
fn test_card_play_stats_impact_score_no_games() {
    let stats = CardPlayStats::default();
    assert_eq!(stats.impact_score(0.5, 100), 0.0);
}

#[test]
fn test_card_play_stats_timing_percentages() {
    let stats = CardPlayStats {
        play_count: 10,
        games_played_in: 5,
        wins_when_played: 3,
        early_plays: 5,
        mid_plays: 3,
        late_plays: 2,
    };

    assert!((stats.early_pct() - 50.0).abs() < 0.1);
    assert!((stats.mid_pct() - 30.0).abs() < 0.1);
    assert!((stats.late_pct() - 20.0).abs() < 0.1);
}

#[test]
fn test_card_play_stats_timing_zero_plays() {
    let stats = CardPlayStats::default();
    assert_eq!(stats.early_pct(), 0.0);
    assert_eq!(stats.mid_pct(), 0.0);
    assert_eq!(stats.late_pct(), 0.0);
}

// ── CardStatsCollector ───────────────────────────────────────────────────────

#[test]
fn test_collector_aggregate_game() {
    let mut collector = CardStatsCollector::new();
    let mut tracker = GameCardTracker::new();

    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);
    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1001), 5);

    collector.aggregate_game(&tracker, Some(PlayerId::PLAYER_ONE));

    // Collector should have stats for both cards
    assert!(collector.stats.contains_key(&CardId(1000)));
    assert!(collector.stats.contains_key(&CardId(1001)));
    assert_eq!(collector.stats[&CardId(1000)].wins_when_played, 1);
}

#[test]
fn test_collector_tracks_losses() {
    let mut collector = CardStatsCollector::new();
    let mut tracker = GameCardTracker::new();

    tracker.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);

    // P1 played the card but P2 won
    collector.aggregate_game(&tracker, Some(PlayerId::PLAYER_TWO));

    assert_eq!(collector.stats[&CardId(1000)].wins_when_played, 0);
    assert_eq!(collector.stats[&CardId(1000)].games_played_in, 1);
}

#[test]
fn test_collector_accumulates_across_games() {
    let mut collector = CardStatsCollector::new();

    // Game 1: P1 plays card, P1 wins
    let mut tracker1 = GameCardTracker::new();
    tracker1.record_play(PlayerId::PLAYER_ONE, CardId(1000), 2);
    collector.aggregate_game(&tracker1, Some(PlayerId::PLAYER_ONE));

    // Game 2: P1 plays same card, P2 wins
    let mut tracker2 = GameCardTracker::new();
    tracker2.record_play(PlayerId::PLAYER_ONE, CardId(1000), 3);
    collector.aggregate_game(&tracker2, Some(PlayerId::PLAYER_TWO));

    assert_eq!(collector.stats[&CardId(1000)].games_played_in, 2);
    assert_eq!(collector.stats[&CardId(1000)].wins_when_played, 1);
    assert!((collector.stats[&CardId(1000)].win_rate() - 0.5).abs() < 0.001);
}
