//! Integration tests for Pickleball ELO Tracker

use pickleball_elo::glicko::{GlickoRating, calculate_new_ratings};
use pickleball_elo::db;

/// Test Glicko-2 rating calculations
#[test]
fn test_glicko_rating_creation() {
    let rating = GlickoRating::new_player();
    assert_eq!(rating.rating, 1500.0);
    assert_eq!(rating.rd, 350.0);
    assert!((rating.volatility - 0.06).abs() < 0.001);
}

#[test]
fn test_glicko_winner_gains_rating() {
    let winner = GlickoRating::new_player();
    let loser = GlickoRating::new_player();
    
    let (new_winner, new_loser) = calculate_new_ratings(&winner, &loser, 1.0, 1.0);
    
    // Winner should gain rating
    assert!(new_winner.rating > winner.rating, 
        "Winner rating {} should be greater than {}", new_winner.rating, winner.rating);
    
    // Loser should lose rating
    assert!(new_loser.rating < loser.rating,
        "Loser rating {} should be less than {}", new_loser.rating, loser.rating);
}

#[test]
fn test_glicko_rating_changes_are_symmetric() {
    let player1 = GlickoRating::new_player();
    let player2 = GlickoRating::new_player();
    
    let (new_p1, new_p2) = calculate_new_ratings(&player1, &player2, 1.0, 1.0);
    
    let p1_change = new_p1.rating - player1.rating;
    let p2_change = new_p2.rating - player2.rating;
    
    // Changes should be roughly symmetric (opposite signs)
    assert!((p1_change + p2_change).abs() < 1.0,
        "Rating changes should be symmetric: {} + {} = {}", p1_change, p2_change, p1_change + p2_change);
}

#[test]
fn test_glicko_bigger_upset_bigger_change() {
    let favorite = GlickoRating { rating: 1800.0, rd: 100.0, volatility: 0.06 };
    let underdog = GlickoRating { rating: 1400.0, rd: 100.0, volatility: 0.06 };
    
    // Underdog wins (upset)
    let (new_underdog, new_favorite) = calculate_new_ratings(&underdog, &favorite, 1.0, 1.0);
    
    // Underdog should gain a lot
    let underdog_gain = new_underdog.rating - underdog.rating;
    assert!(underdog_gain > 20.0, 
        "Underdog upset gain {} should be significant", underdog_gain);
}

#[test]
fn test_glicko_rd_decreases_after_match() {
    let player1 = GlickoRating { rating: 1500.0, rd: 200.0, volatility: 0.06 };
    let player2 = GlickoRating { rating: 1500.0, rd: 200.0, volatility: 0.06 };
    
    let (new_p1, _) = calculate_new_ratings(&player1, &player2, 1.0, 1.0);
    
    // RD should decrease after playing (more certainty)
    assert!(new_p1.rd < player1.rd,
        "RD {} should decrease from {}", new_p1.rd, player1.rd);
}

#[test]
fn test_score_weighting_blowout_vs_close() {
    let player1 = GlickoRating::new_player();
    let player2 = GlickoRating::new_player();
    
    // Blowout win (11-0)
    let (blowout_winner, _) = calculate_new_ratings(&player1, &player2, 1.0, 1.5);
    
    // Close win (11-9)
    let (close_winner, _) = calculate_new_ratings(&player1, &player2, 1.0, 1.05);
    
    // Blowout should give more rating
    assert!(blowout_winner.rating > close_winner.rating,
        "Blowout {} should give more than close {}", blowout_winner.rating, close_winner.rating);
}

/// Test database operations
#[tokio::test]
async fn test_database_creation() {
    let temp_dir = std::env::temp_dir();
    let db_path = temp_dir.join("test_pickleball.db");
    let db_str = db_path.to_str().unwrap();
    
    // Clean up from previous runs
    let _ = std::fs::remove_file(&db_path);
    
    let pool = db::create_pool(db_str).await.expect("Failed to create pool");
    
    // Verify tables exist
    let result: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM players")
        .fetch_one(&pool)
        .await
        .expect("Players table should exist");
    
    assert_eq!(result.0, 0, "Players table should be empty");
    
    // Clean up
    drop(pool);
    let _ = std::fs::remove_file(&db_path);
}

#[tokio::test]
async fn test_player_crud() {
    let temp_dir = std::env::temp_dir();
    let db_path = temp_dir.join("test_crud.db");
    let db_str = db_path.to_str().unwrap();
    let _ = std::fs::remove_file(&db_path);
    
    let pool = db::create_pool(db_str).await.unwrap();
    
    // Create player
    sqlx::query("INSERT INTO players (name, email) VALUES ('Test Player', 'test@example.com')")
        .execute(&pool)
        .await
        .expect("Should insert player");
    
    // Read player
    let player: (i64, String, Option<String>, f64) = sqlx::query_as(
        "SELECT id, name, email, singles_rating FROM players WHERE name = 'Test Player'"
    )
    .fetch_one(&pool)
    .await
    .expect("Should find player");
    
    assert_eq!(player.1, "Test Player");
    assert_eq!(player.2, Some("test@example.com".to_string()));
    assert_eq!(player.3, 1500.0); // Default rating
    
    // Update player
    sqlx::query("UPDATE players SET singles_rating = 1600.0 WHERE id = ?")
        .bind(player.0)
        .execute(&pool)
        .await
        .expect("Should update player");
    
    let updated: (f64,) = sqlx::query_as("SELECT singles_rating FROM players WHERE id = ?")
        .bind(player.0)
        .fetch_one(&pool)
        .await
        .unwrap();
    
    assert_eq!(updated.0, 1600.0);
    
    // Delete player
    sqlx::query("DELETE FROM players WHERE id = ?")
        .bind(player.0)
        .execute(&pool)
        .await
        .expect("Should delete player");
    
    let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM players")
        .fetch_one(&pool)
        .await
        .unwrap();
    
    assert_eq!(count.0, 0);
    
    drop(pool);
    let _ = std::fs::remove_file(&db_path);
}

#[tokio::test]
async fn test_match_recording() {
    let temp_dir = std::env::temp_dir();
    let db_path = temp_dir.join("test_matches.db");
    let db_str = db_path.to_str().unwrap();
    let _ = std::fs::remove_file(&db_path);
    
    let pool = db::create_pool(db_str).await.unwrap();
    
    // Create two players
    sqlx::query("INSERT INTO players (name) VALUES ('Player A')")
        .execute(&pool).await.unwrap();
    sqlx::query("INSERT INTO players (name) VALUES ('Player B')")
        .execute(&pool).await.unwrap();
    
    // Create a session
    let session_id: i64 = sqlx::query_scalar(
        "INSERT INTO sessions (notes) VALUES ('Test Session') RETURNING id"
    )
    .fetch_one(&pool)
    .await
    .unwrap();
    
    // Create a match
    let match_id: i64 = sqlx::query_scalar(
        "INSERT INTO matches (session_id, match_type, team1_score, team2_score) VALUES (?, 'singles', 11, 5) RETURNING id"
    )
    .bind(session_id)
    .fetch_one(&pool)
    .await
    .unwrap();
    
    assert!(match_id > 0, "Match should be created with valid ID");
    
    // Verify match
    let match_data: (String, i32, i32) = sqlx::query_as(
        "SELECT match_type, team1_score, team2_score FROM matches WHERE id = ?"
    )
    .bind(match_id)
    .fetch_one(&pool)
    .await
    .unwrap();
    
    assert_eq!(match_data.0, "singles");
    assert_eq!(match_data.1, 11);
    assert_eq!(match_data.2, 5);
    
    drop(pool);
    let _ = std::fs::remove_file(&db_path);
}

#[test]
fn test_rating_bounds() {
    // Test that ratings don't go below 0 or above unreasonable values
    let very_low = GlickoRating { rating: 100.0, rd: 50.0, volatility: 0.06 };
    let very_high = GlickoRating { rating: 2500.0, rd: 50.0, volatility: 0.06 };
    
    let (new_low, _) = calculate_new_ratings(&very_low, &very_high, 0.0, 1.0);
    
    assert!(new_low.rating > 0.0, "Rating should stay positive");
    assert!(new_low.rd > 0.0, "RD should stay positive");
}

#[test]
fn test_draw_handling() {
    let player1 = GlickoRating::new_player();
    let player2 = GlickoRating::new_player();
    
    // Score of 0.5 = draw
    let (new_p1, new_p2) = calculate_new_ratings(&player1, &player2, 0.5, 1.0);
    
    // In a draw between equal players, ratings shouldn't change much
    let p1_change = (new_p1.rating - player1.rating).abs();
    let p2_change = (new_p2.rating - player2.rating).abs();
    
    assert!(p1_change < 1.0, "Draw should not change rating much: {}", p1_change);
    assert!(p2_change < 1.0, "Draw should not change rating much: {}", p2_change);
}
