//! Command handlers for peer commands.

use std::{collections::HashSet, net::SocketAddr, sync::Arc};

use lanspread_db::db::{Game, GameDB, GameFileDescription};
use tokio::sync::{RwLock, mpsc::UnboundedSender};

use crate::{
    PeerEvent,
    context::Ctx,
    download::download_game_files,
    local_games::{get_game_file_descriptions, load_local_game_db, local_download_available},
    network::{announce_games_to_peer, request_game_details_from_peer},
    peer_db::PeerGameDB,
};

// =============================================================================
// Command handlers
// =============================================================================

/// Handles the `ListGames` command.
pub async fn handle_list_games_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
    log::info!("ListGames command received");
    emit_peer_game_list(&ctx.peer_game_db, tx_notify_ui).await;
}

/// Emits the aggregated game list to the UI.
pub async fn emit_peer_game_list(
    peer_game_db: &Arc<RwLock<PeerGameDB>>,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
) {
    let all_games = { peer_game_db.read().await.get_all_games() };
    if let Err(e) = tx_notify_ui.send(PeerEvent::ListGames(all_games)) {
        log::error!("Failed to send ListGames event: {e}");
    }
}

/// Tries to serve a game from local files.
async fn try_serve_local_game(
    ctx: &Ctx,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
    id: &str,
) -> bool {
    let game_dir = { ctx.game_dir.read().await.clone() };
    let Some(game_dir) = game_dir else {
        return false;
    };

    let downloading = ctx.downloading_games.read().await;
    if !local_download_available(&game_dir, id, &downloading).await {
        return false;
    }
    drop(downloading);

    match get_game_file_descriptions(id, &game_dir).await {
        Ok(file_descriptions) => {
            log::info!("Serving game {id} from local files");
            if let Err(e) = tx_notify_ui.send(PeerEvent::GotGameFiles {
                id: id.to_string(),
                file_descriptions,
            }) {
                log::error!("Failed to send GotGameFiles event: {e}");
            }
            true
        }
        Err(e) => {
            log::error!("Failed to enumerate local file descriptions for {id}: {e}");
            false
        }
    }
}

/// Handles the `GetGame` command.
pub async fn handle_get_game_command(
    ctx: &Ctx,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
    id: String,
) {
    if try_serve_local_game(ctx, tx_notify_ui, &id).await {
        return;
    }

    log::info!("Requesting game from peers: {id}");
    let peers = { ctx.peer_game_db.read().await.peers_with_game(&id) };
    if peers.is_empty() {
        log::warn!("No peers have game {id}");
        if let Err(e) = tx_notify_ui.send(PeerEvent::NoPeersHaveGame { id: id.clone() }) {
            log::error!("Failed to send NoPeersHaveGame event: {e}");
        }
        return;
    }

    let peer_game_db = ctx.peer_game_db.clone();
    let tx_notify_ui = tx_notify_ui.clone();
    tokio::spawn(async move {
        let mut fetched_any = false;
        for peer_addr in peers {
            match request_game_details_and_update(peer_addr, &id, peer_game_db.clone()).await {
                Ok(_) => {
                    log::info!("Fetched game file list for {id} from peer {peer_addr}");
                    fetched_any = true;
                }
                Err(e) => {
                    log::error!("Failed to fetch game files for {id} from {peer_addr}: {e}");
                }
            }
        }

        if fetched_any {
            let aggregated_files = { peer_game_db.read().await.aggregated_game_files(&id) };

            if let Err(e) = tx_notify_ui.send(PeerEvent::GotGameFiles {
                id: id.clone(),
                file_descriptions: aggregated_files,
            }) {
                log::error!("Failed to send GotGameFiles event: {e}");
            }
        } else {
            log::warn!("Failed to retrieve game files for {id} from any peer");
        }
    });
}

/// Requests game details from a peer and updates the peer game database.
async fn request_game_details_and_update(
    peer_addr: SocketAddr,
    game_id: &str,
    peer_game_db: Arc<RwLock<PeerGameDB>>,
) -> eyre::Result<Vec<GameFileDescription>> {
    let (file_descriptions, _) = request_game_details_from_peer(peer_addr, game_id).await?;

    {
        let mut db = peer_game_db.write().await;
        db.update_peer_game_files(peer_addr, game_id, file_descriptions.clone());
    }

    Ok(file_descriptions)
}

/// Handles the `DownloadGameFiles` command.
#[allow(clippy::too_many_lines)]
pub async fn handle_download_game_files_command(
    ctx: &Ctx,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
    id: String,
    file_descriptions: Vec<GameFileDescription>,
) {
    log::info!("Got PeerCommand::DownloadGameFiles");
    let games_folder = { ctx.game_dir.read().await.clone() };
    if games_folder.is_none() {
        log::error!("Cannot handle game file descriptions: games_folder is not set");
        return;
    }

    let games_folder = games_folder.expect("checked above");

    // Use majority validation to get trusted file descriptions and peer whitelist
    let (validated_descriptions, peer_whitelist, file_peer_map) = {
        match ctx
            .peer_game_db
            .read()
            .await
            .validate_file_sizes_majority(&id)
        {
            Ok((files, peers, file_peer_map)) => {
                log::info!(
                    "Majority validation: {} validated files, {} trusted peers for game {id}",
                    files.len(),
                    peers.len()
                );
                (files, peers, file_peer_map)
            }
            Err(e) => {
                log::error!("File size majority validation failed for {id}: {e}");
                if let Err(send_err) =
                    tx_notify_ui.send(PeerEvent::DownloadGameFilesFailed { id: id.clone() })
                {
                    log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
                }
                return;
            }
        }
    };

    let resolved_descriptions = if file_descriptions.is_empty() {
        validated_descriptions
    } else {
        // If user provided specific descriptions, still validate them against majority
        // but keep user's selection (they might want specific files)
        file_descriptions
    };

    if resolved_descriptions.is_empty() {
        log::error!(
            "No validated file descriptions available to download game {id}; request metadata first"
        );
        return;
    }

    let downloading = ctx.downloading_games.read().await;
    if peer_whitelist.is_empty() {
        if local_download_available(&games_folder, &id, &downloading).await {
            drop(downloading);
            log::info!("Using locally downloaded files for game {id}; skipping peer transfer");
            if let Err(e) = tx_notify_ui.send(PeerEvent::DownloadGameFilesBegin { id: id.clone() })
            {
                log::error!("Failed to send DownloadGameFilesBegin event: {e}");
            }
            if let Err(e) =
                tx_notify_ui.send(PeerEvent::DownloadGameFilesFinished { id: id.clone() })
            {
                log::error!("Failed to send DownloadGameFilesFinished event: {e}");
            }
        } else {
            log::error!("No trusted peers available after majority validation for game {id}");
        }
        return;
    }
    drop(downloading);

    {
        let mut in_progress = ctx.downloading_games.write().await;
        if !in_progress.insert(id.clone()) {
            log::warn!("Download for {id} already in progress; ignoring new request");
            return;
        }
    }

    let downloading_games = ctx.downloading_games.clone();
    let active_downloads = ctx.active_downloads.clone();
    let tx_notify_ui_clone = tx_notify_ui.clone();
    let download_id = id.clone();

    let handle = tokio::spawn(async move {
        let result = download_game_files(
            &download_id,
            resolved_descriptions,
            games_folder,
            peer_whitelist,
            file_peer_map,
            tx_notify_ui_clone.clone(),
        )
        .await;

        {
            let mut guard = downloading_games.write().await;
            guard.remove(&download_id);
        }

        if let Err(e) = result {
            log::error!("Download failed for {download_id}: {e}");
            if let Err(send_err) = tx_notify_ui_clone.send(PeerEvent::DownloadGameFilesFailed {
                id: download_id.clone(),
            }) {
                log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
            }
        }

        let _ = active_downloads.write().await.remove(&download_id);
    });

    ctx.active_downloads.write().await.insert(id, handle);
}

/// Handles the `SetGameDir` command.
pub async fn handle_set_game_dir_command(
    ctx: &Ctx,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
    game_dir: String,
) {
    *ctx.game_dir.write().await = Some(game_dir.clone());
    log::info!("Game directory set to: {game_dir}");

    // Load local game database when game directory is set
    let game_dir = game_dir.clone();
    let tx_notify_ui = tx_notify_ui.clone();
    let ctx_clone = ctx.clone();

    tokio::spawn(async move {
        match load_local_game_db(&game_dir).await {
            Ok(db) => {
                update_and_announce_games(&ctx_clone, &tx_notify_ui, db).await;
                log::info!("Local game database loaded successfully");
            }
            Err(e) => {
                log::error!("Failed to load local game database: {e}");
            }
        }
    });
}

/// Handles the `GetPeerCount` command.
pub async fn handle_get_peer_count_command(ctx: &Ctx, tx_notify_ui: &UnboundedSender<PeerEvent>) {
    log::info!("GetPeerCount command received");
    let peer_count = { ctx.peer_game_db.read().await.get_peer_addresses().len() };
    if let Err(e) = tx_notify_ui.send(PeerEvent::PeerCountUpdated(peer_count)) {
        log::error!("Failed to send PeerCountUpdated event: {e}");
    }
}

// =============================================================================
// Game announcement helpers
// =============================================================================

/// Updates the local game database and announces changes to peers.
pub async fn update_and_announce_games(
    ctx: &Ctx,
    tx_notify_ui: &UnboundedSender<PeerEvent>,
    new_db: GameDB,
) {
    let local_game_db = ctx.local_game_db.clone();
    let mut db_guard = local_game_db.write().await;

    let previous_games = db_guard
        .as_ref()
        .map(|db| db.games.keys().cloned().collect::<HashSet<_>>())
        .unwrap_or_default();

    let current_game_ids = new_db.games.keys().cloned().collect::<HashSet<_>>();

    // Check if any games were removed
    let removed_games: Vec<String> = previous_games
        .difference(&current_game_ids)
        .cloned()
        .collect();

    if removed_games.is_empty() {
        // Check if any games were added or updated
        if previous_games != current_game_ids {
            log::debug!("Local games directory structure changed, updating database");
            *db_guard = Some(new_db);

            let all_games = db_guard
                .as_ref()
                .map(|db| db.all_games().into_iter().cloned().collect::<Vec<Game>>())
                .unwrap_or_default();

            if let Err(e) = tx_notify_ui.send(PeerEvent::LocalGamesUpdated(all_games.clone())) {
                log::error!("Failed to send LocalGamesUpdated event: {e}");
            }

            // Broadcast update to all peers
            let peer_addresses = { ctx.peer_game_db.read().await.get_peer_addresses() };
            for peer_addr in peer_addresses {
                let games_clone = all_games.clone();
                tokio::spawn(async move {
                    if let Err(e) = announce_games_to_peer(peer_addr, games_clone).await {
                        log::warn!("Failed to announce games to {peer_addr}: {e}");
                    }
                });
            }
        }
    } else {
        log::info!("Detected removed games: {removed_games:?}");
        *db_guard = Some(new_db);

        // Notify UI about the change
        let all_games = db_guard
            .as_ref()
            .map(|db| db.all_games().into_iter().cloned().collect::<Vec<Game>>())
            .unwrap_or_default();

        if let Err(e) = tx_notify_ui.send(PeerEvent::LocalGamesUpdated(all_games.clone())) {
            log::error!("Failed to send LocalGamesUpdated event: {e}");
        }

        // Broadcast update to all peers
        let peer_addresses = { ctx.peer_game_db.read().await.get_peer_addresses() };
        for peer_addr in peer_addresses {
            let games_clone = all_games.clone();
            tokio::spawn(async move {
                if let Err(e) = announce_games_to_peer(peer_addr, games_clone).await {
                    log::warn!("Failed to announce games to {peer_addr}: {e}");
                }
            });
        }
    }
}
