//! Daemon core logic.
//!
//! The daemon is the main runtime that:
//! - Manages a persistent Ghidra bridge process
//! - Serves commands via local socket IPC
//! - Handles graceful shutdown

use std::path::PathBuf;
use std::sync::Arc;

use anyhow::{Context, Result};
use tokio::sync::{broadcast, Mutex};
use tracing::{error, info, warn};

use crate::daemon::process::{write_daemon_info, remove_lock_file, DaemonInfo, get_data_dir};
use crate::ghidra::bridge::GhidraBridge;

pub mod cache;
pub mod handler;
pub mod ipc_server;
pub mod process;
pub mod queue;
pub mod rpc;
pub mod state;

/// Daemon configuration.
pub struct DaemonConfig {
    /// Path to the project directory
    pub project_path: PathBuf,
    /// RPC port (None = auto-select) - kept for backwards compatibility
    pub port: Option<u16>,
    /// Ghidra installation directory
    pub ghidra_install_dir: Option<PathBuf>,
    /// Log file path
    pub log_file: PathBuf,
    /// Program name to load
    pub program_name: Option<String>,
}

/// Run the daemon with the new bridge architecture.
pub async fn run(config: DaemonConfig) -> Result<()> {
    info!("Starting Ghidra daemon");
    info!("Project: {}", config.project_path.display());

    // Get data directory
    let data_dir = get_data_dir()
        .context("Failed to get data directory")?;

    // Create shutdown channel
    let (shutdown_tx, _shutdown_rx) = broadcast::channel::<()>(1);

    // Initialize the Ghidra bridge (starts as None until we have a program)
    let bridge: Arc<Mutex<Option<GhidraBridge>>> = Arc::new(Mutex::new(None));

    // If we have Ghidra install dir and program name, start the bridge
    if let (Some(ghidra_dir), Some(program_name)) = (&config.ghidra_install_dir, &config.program_name) {
        info!("Starting Ghidra bridge for program: {}", program_name);
        
        let project_name = config.project_path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("project")
            .to_string();
        
        let mut new_bridge = GhidraBridge::new(
            ghidra_dir.clone(),
            config.project_path.clone(),
            project_name,
            program_name.clone(),
        );
        
        if let Err(e) = new_bridge.start() {
            warn!("Failed to start Ghidra bridge: {}. Will operate without persistent connection.", e);
        } else {
            info!("Ghidra bridge started successfully");
            let mut bridge_guard = bridge.lock().await;
            *bridge_guard = Some(new_bridge);
        }
    } else {
        info!("No program specified, bridge will be started on first command");
    }

    // Write lock file with a placeholder port (IPC doesn't use TCP ports)
    let placeholder_port = config.port.unwrap_or(0);
    let daemon_info = DaemonInfo::new(&config.project_path, placeholder_port, &config.log_file);
    write_daemon_info(&data_dir, &config.project_path, &daemon_info)
        .context("Failed to write lock file")?;

    // Start IPC server task
    let ipc_bridge = bridge.clone();
    let ipc_shutdown_tx = shutdown_tx.clone();
    let ipc_handle = tokio::spawn(async move {
        if let Err(e) = ipc_server::run_ipc_server(ipc_bridge, ipc_shutdown_tx).await {
            error!("IPC server error: {}", e);
        }
    });

    // Also start the legacy RPC server for backwards compatibility
    let queue = Arc::new(queue::CommandQueue::new(config.project_path.clone(), bridge.clone()));
    let rpc_port = rpc::run_server(queue.clone(), config.port, shutdown_tx.clone()).await
        .context("Failed to start RPC server")?;
    info!("Legacy RPC server listening on port {} (for backwards compatibility)", rpc_port);

    // Wait for shutdown signal
    let shutdown_reason = wait_for_shutdown(shutdown_tx.clone()).await;

    info!("Shutdown initiated: {:?}", shutdown_reason);

    // Clean up
    shutdown_tx.send(()).ok(); // Signal all tasks to stop

    // Stop the bridge
    {
        let mut bridge_guard = bridge.lock().await;
        if let Some(mut b) = bridge_guard.take() {
            info!("Stopping Ghidra bridge...");
            if let Err(e) = b.stop() {
                error!("Error stopping bridge: {}", e);
            }
        }
    }

    // Wait for IPC server to stop (with timeout)
    tokio::select! {
        _ = ipc_handle => {
            info!("IPC server stopped");
        }
        _ = tokio::time::sleep(tokio::time::Duration::from_secs(5)) => {
            warn!("IPC server did not stop in time");
        }
    }

    // Remove lock file
    remove_lock_file(&data_dir, &config.project_path)
        .context("Failed to remove lock file")?;

    info!("Daemon stopped");
    Ok(())
}

/// The reason for shutdown.
#[derive(Debug, Clone)]
pub enum ShutdownReason {
    /// SIGINT (Ctrl+C)
    Interrupt,
    /// SIGTERM
    Terminate,
    /// RPC shutdown request
    RpcRequest,
}

/// Wait for a shutdown signal.
async fn wait_for_shutdown(shutdown_tx: broadcast::Sender<()>) -> ShutdownReason {
    let mut shutdown_rx = shutdown_tx.subscribe();

    #[cfg(unix)]
    {
        use tokio::signal::unix::{signal, SignalKind};

        let mut sigint = signal(SignalKind::interrupt())
            .expect("Failed to register SIGINT handler");
        let mut sigterm = signal(SignalKind::terminate())
            .expect("Failed to register SIGTERM handler");

        tokio::select! {
            _ = sigint.recv() => {
                info!("Received SIGINT");
                ShutdownReason::Interrupt
            }
            _ = sigterm.recv() => {
                info!("Received SIGTERM");
                ShutdownReason::Terminate
            }
            _ = shutdown_rx.recv() => {
                info!("Received shutdown request via RPC");
                ShutdownReason::RpcRequest
            }
        }
    }

    #[cfg(windows)]
    {
        use tokio::signal;

        tokio::select! {
            _ = signal::ctrl_c() => {
                info!("Received Ctrl+C");
                ShutdownReason::Interrupt
            }
            _ = shutdown_rx.recv() => {
                info!("Received shutdown request via RPC");
                ShutdownReason::RpcRequest
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_daemon_config() {
        let config = DaemonConfig {
            project_path: PathBuf::from("/test/project"),
            port: Some(17700),
            ghidra_install_dir: None,
            log_file: PathBuf::from("/test/logs/daemon.log"),
            program_name: None,
        };

        assert_eq!(config.port, Some(17700));
    }
}
