use crate::database; use crate::games::registry::get_game_registry; use crate::games::session::SessionManager; use crate::games::traits::{GameError, GamePhase}; use crate::tls::TlsStream; use once_cell::sync::Lazy; use parking_lot::Mutex; use rusqlite::Connection; use std::io::{BufRead, BufReader, Write}; use std::net::TcpStream; static DB_CONN: Lazy> = Lazy::new(|| { let conn = Connection::open( std::env::var("SERVER_DB").unwrap_or_else(|_| "server.db".to_string()), ) .unwrap(); database::init_db(&conn); Mutex::new(conn) }); static SESSIONS: Lazy = Lazy::new(SessionManager::new); pub fn handle_client(stream: TcpStream) { let client_addr = stream.peer_addr().unwrap().to_string(); let mut user = "guest".to_string(); println!("[{} @ {}] Client connected", user, client_addr); let reader_stream = stream.try_clone().unwrap(); let reader = BufReader::new(reader_stream); let mut writer = stream; for line in reader.lines() { let line = line.unwrap(); println!("[{} @ {}] Received: {}", user, client_addr, line); let response = process_message(&line, &mut user); writeln!(writer, "{}", response).unwrap(); println!("[{} @ {}] Sent: {}", user, client_addr, response); } println!("[{} @ {}] Client disconnected", user, client_addr); } fn process_message(msg: &str, user: &mut String) -> String { let parts: Vec<&str> = msg.splitn(5, ' ').collect(); match parts[0] { "GAME_LIST" => { let registry = get_game_registry(); let games: Vec = registry.list_games(); if games.is_empty() { "ERROR No games available".to_string() } else { let game_info: Vec = games .iter() .filter_map(|gt| { registry .get_game(gt) .map(|g| format!("{}:{}", gt, g.get_name())) }) .collect(); format!("GAME_LIST {}", game_info.join(",")) } } "GAME_INFO" => { if parts.len() != 2 { "ERROR Invalid GAME_INFO format. Usage: GAME_INFO ".to_string() } else { let registry = get_game_registry(); match registry.get_game(parts[1]) { Some(game) => format!( "GAME_INFO {} {} {}", parts[1], game.get_name(), game.get_description() ), None => format!("ERROR Unknown game type: {}", parts[1]), } } } "PLAY_START" => { if parts.len() != 3 { "ERROR Invalid PLAY_START format. Usage: PLAY_START " .to_string() } else { handle_play_start(user, parts[1], parts[2]) } } "REGISTER" => { if parts.len() != 3 { "ERROR Invalid REGISTER format".to_string() } else { let username = parts[1]; let hashed_password = parts[2]; let db = DB_CONN.lock(); match database::add_user(&db, username, hashed_password) { Ok(_) => format!("REGISTERED {}", username), Err(_) => "ERROR User already exists".to_string(), } } } "AUTH" => { if parts.len() != 3 { "ERROR Invalid AUTH format".to_string() } else { let username = parts[1]; let sent_hash = parts[2]; let db = DB_CONN.lock(); if let Some(stored_hash) = database::get_hashed_password(&db, username) { if sent_hash == stored_hash { *user = username.to_string(); format!("OK {}", username) } else { "ERROR Invalid credentials".to_string() } } else { "ERROR User not found".to_string() } } } "PING" => "PONG".to_string(), "INFO" => { if *user == "guest" { "ERROR Not logged in".to_string() } else { let db = DB_CONN.lock(); if let (Some(id), Some(balance)) = ( database::get_account_id(&db, user), database::get_balance(&db, user), ) { format!("INFO {} {} {}", user, id, balance) } else { "ERROR Error retrieving user info".to_string() } } } _ => msg.to_string(), } } fn handle_play_start(user: &str, game_type: &str, bet_amount_str: &str) -> String { if user == "guest" { return "ERROR Not logged in".to_string(); } let bet_amount: i64 = match bet_amount_str.parse() { Ok(amount) => amount, Err(_) => return "ERROR Invalid bet amount".to_string(), }; if bet_amount <= 0 { return "ERROR Invalid bet amount".to_string(); } let registry = get_game_registry(); let game = match registry.get_game(game_type) { Some(game) => game, None => return format!("ERROR Unknown game type: {}", game_type), }; let db = DB_CONN.lock(); // Validate balance inside a read. let current_balance = match database::get_balance(&db, user) { Some(balance) => balance, None => return "ERROR Could not retrieve balance".to_string(), }; if let Err(e) = game.validate_bet(bet_amount as u64, current_balance as u64) { return match e { GameError::InsufficientBalance => "ERROR Insufficient balance".to_string(), GameError::InvalidBet => "ERROR Invalid bet amount".to_string(), _ => "ERROR Bet validation failed".to_string(), }; } // Prepare and spin. let game_state = game.prepare_state(user, bet_amount as u64); let new_state = match game.process_action(&game_state, "spin", &[]) { Ok(state) => state, Err(e) => return format!("ERROR Failed to spin: {:?}", e), }; let result = match game.calculate_result(&new_state) { Ok(result) => result, Err(e) => return format!("ERROR Failed to calculate result: {:?}", e), }; // Create session before touching balance. let session_id = SESSIONS.create_session(user, game_type.to_string(), new_state.clone()); // All balance mutations in a single transaction. let total_return = result.winnings as i64; let tx_result = db.execute_batch(&format!( "UPDATE users SET balance = balance - {bet_amount} WHERE username = '{user}'; UPDATE users SET balance = balance + {total_return} WHERE username = '{user}';" )); if let Err(e) = tx_result { let _ = SESSIONS.remove_session(&session_id); eprintln!("Balance transaction failed for {user}: {e}"); return "ERROR Failed to update balance".to_string(); } let new_balance = match database::get_balance(&db, user) { Some(balance) => balance, None => return "ERROR Could not retrieve new balance".to_string(), }; let reels = new_state.data.get("reels").cloned().unwrap_or_default(); let win_type = new_state.data.get("win_type").cloned().unwrap_or_default(); format!( "GAME_RESULT {session_id} {} {} {new_balance} {reels} {win_type}", result.winnings, result.bet_amount, ) } pub fn handle_client_tls(mut stream: TlsStream) { let client_addr = stream.peer_addr().unwrap().to_string(); let mut user = "guest".to_string(); println!("[{} @ {}] TLS Client connected", user, client_addr); loop { let mut line = String::new(); match stream.read_line(&mut line) { Ok(0) => break, Ok(_) => {} Err(e) => { eprintln!("Read error: {}", e); break; } } let line = line.trim(); if line.is_empty() { continue; } println!("[{} @ {}] Received: {}", user, client_addr, line); let response = process_message(line, &mut user); if let Err(e) = stream.write_line(&response) { eprintln!("Write error: {}", e); break; } println!("[{} @ {}] Sent: {}", user, client_addr, response); } // Clean up any active game sessions for this user on disconnect. if user != "guest" { for session in SESSIONS.get_user_sessions(&user) { let _ = SESSIONS.remove_session(&session.session_id); } } println!("[{} @ {}] TLS Client disconnected", user, client_addr); }