//! Peer-to-peer game distribution system.
//!
//! This crate provides the core P2P networking engine for `LanSpread`, handling:
//! - Peer discovery via mDNS
//! - QUIC-based communication
//! - Game file synchronization with chunked transfers
//! - Consensus validation for file integrity

#![allow(clippy::missing_errors_doc)]

// =============================================================================
// Module declarations
// =============================================================================

mod config;
mod context;
mod download;
mod error;
mod handlers;
mod local_games;
mod network;
mod path_validation;
mod peer;
mod peer_db;
mod services;

// =============================================================================
// Public re-exports
// =============================================================================

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

pub use config::{CHUNK_SIZE, MAX_RETRY_COUNT};
pub use error::PeerError;
use lanspread_db::db::{Game, GameFileDescription};
pub use peer_db::{MajorityValidationResult, PeerGameDB, PeerInfo};
use tokio::sync::{
    RwLock,
    mpsc::{UnboundedReceiver, UnboundedSender},
};

use crate::{
    context::Ctx,
    handlers::{
        handle_download_game_files_command,
        handle_get_game_command,
        handle_get_peer_count_command,
        handle_list_games_command,
        handle_set_game_dir_command,
    },
    services::{
        run_local_game_monitor,
        run_peer_discovery,
        run_ping_service,
        run_server_component,
    },
};

// =============================================================================
// Public API types
// =============================================================================

/// Events sent from the peer system to the UI.
#[derive(Debug)]
pub enum PeerEvent {
    /// List of available games from peers.
    ListGames(Vec<Game>),
    /// File descriptions for a specific game.
    GotGameFiles {
        id: String,
        file_descriptions: Vec<GameFileDescription>,
    },
    /// Download has started for a game.
    DownloadGameFilesBegin { id: String },
    /// Download has completed successfully.
    DownloadGameFilesFinished { id: String },
    /// Download has failed.
    DownloadGameFilesFailed { id: String },
    /// All peers with the game have disconnected during download.
    DownloadGameFilesAllPeersGone { id: String },
    /// No peers have the requested game.
    NoPeersHaveGame { id: String },
    /// A peer has connected.
    PeerConnected(SocketAddr),
    /// A peer has disconnected.
    PeerDisconnected(SocketAddr),
    /// A new peer was discovered via mDNS.
    PeerDiscovered(SocketAddr),
    /// A peer was lost (timed out or disconnected).
    PeerLost(SocketAddr),
    /// The total peer count has changed.
    PeerCountUpdated(usize),
    /// Local games have been updated.
    LocalGamesUpdated(Vec<Game>),
}

/// Commands sent to the peer system from the UI.
#[derive(Clone, Debug)]
pub enum PeerCommand {
    /// Request a list of all available games.
    ListGames,
    /// Request file details for a specific game.
    GetGame(String),
    /// Download game files.
    DownloadGameFiles {
        id: String,
        file_descriptions: Vec<GameFileDescription>,
    },
    /// Set the local game directory.
    SetGameDir(String),
    /// Request the current peer count.
    GetPeerCount,
}

// =============================================================================
// Public API functions
// =============================================================================

/// Initialize and start the peer system.
///
/// This is the main entry point for the peer system. It starts all background
/// services (server, discovery, ping, local monitor) and returns a channel
/// for sending commands.
///
/// # Arguments
///
/// * `game_dir` - Path to the local game directory
/// * `tx_notify_ui` - Channel for sending events to the UI
/// * `peer_game_db` - Shared peer game database
///
/// # Returns
///
/// A channel sender for sending commands to the peer system.
pub fn start_peer(
    game_dir: String,
    tx_notify_ui: UnboundedSender<PeerEvent>,
    peer_game_db: Arc<RwLock<PeerGameDB>>,
) -> eyre::Result<UnboundedSender<PeerCommand>> {
    log::info!("Starting peer system with game directory: {game_dir}");

    let (tx_control, rx_control) = tokio::sync::mpsc::unbounded_channel();

    // Start the peer in a background task
    let tx_control_clone = tx_control.clone();
    tokio::spawn(async move {
        if let Err(e) = run_peer(rx_control, tx_notify_ui, peer_game_db).await {
            log::error!("Peer system failed: {e}");
        }
    });

    // Set the game directory
    tx_control.send(PeerCommand::SetGameDir(game_dir))?;

    Ok(tx_control_clone)
}

/// Main peer execution loop that handles peer commands and manages the peer system.
async fn run_peer(
    mut rx_control: UnboundedReceiver<PeerCommand>,
    tx_notify_ui: UnboundedSender<PeerEvent>,
    peer_game_db: Arc<RwLock<PeerGameDB>>,
) -> eyre::Result<()> {
    // Create the shared context
    let ctx = Ctx::new(peer_game_db.clone());
    let peer_ctx = ctx.to_peer_ctx(tx_notify_ui.clone());

    // Start server component
    let server_addr = "0.0.0.0:0".parse::<SocketAddr>()?;
    let tx_notify_ui_clone = tx_notify_ui.clone();
    let peer_ctx_clone = peer_ctx.clone();

    tokio::spawn(async move {
        if let Err(e) = run_server_component(server_addr, peer_ctx_clone, tx_notify_ui_clone).await
        {
            log::error!("Server component error: {e}");
        }
    });

    // Start peer discovery task
    let tx_notify_ui_discovery = tx_notify_ui.clone();
    let peer_game_db_discovery = ctx.peer_game_db.clone();
    let local_peer_addr = ctx.local_peer_addr.clone();
    tokio::spawn(async move {
        run_peer_discovery(
            tx_notify_ui_discovery,
            peer_game_db_discovery,
            local_peer_addr,
        )
        .await;
    });

    // Start ping service task
    let tx_notify_ui_ping = tx_notify_ui.clone();
    let peer_game_db_ping = ctx.peer_game_db.clone();
    let downloading_games_ping = ctx.downloading_games.clone();
    let active_downloads_ping = ctx.active_downloads.clone();
    tokio::spawn(async move {
        run_ping_service(
            tx_notify_ui_ping,
            peer_game_db_ping,
            downloading_games_ping,
            active_downloads_ping,
        )
        .await;
    });

    // Start local game directory monitoring task
    let tx_notify_ui_monitor = tx_notify_ui.clone();
    let ctx_monitor = ctx.clone();
    tokio::spawn(async move {
        run_local_game_monitor(tx_notify_ui_monitor, ctx_monitor).await;
    });

    // Handle client commands
    loop {
        let Some(cmd) = rx_control.recv().await else {
            break;
        };

        match cmd {
            PeerCommand::ListGames => {
                handle_list_games_command(&ctx, &tx_notify_ui).await;
            }
            PeerCommand::GetGame(id) => {
                handle_get_game_command(&ctx, &tx_notify_ui, id).await;
            }
            PeerCommand::DownloadGameFiles {
                id,
                file_descriptions,
            } => {
                handle_download_game_files_command(&ctx, &tx_notify_ui, id, file_descriptions)
                    .await;
            }
            PeerCommand::SetGameDir(game_dir) => {
                handle_set_game_dir_command(&ctx, &tx_notify_ui, game_dir).await;
            }
            PeerCommand::GetPeerCount => {
                handle_get_peer_count_command(&ctx, &tx_notify_ui).await;
            }
        }
    }

    Ok(())
}
