gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! Daemon command handlers.

use crate::cli::args::{
    DaemonCommand, DaemonInstallArgs, DaemonLogsArgs, DaemonStartArgs, GlobalArgs, ServiceType,
};
use crate::cli::output::OutputWriter;
use crate::daemon::process::{
    generate_launchd_plist, generate_systemd_service, get_daemon_status, read_daemon_logs,
    restart_daemon, start_daemon, stop_daemon,
};
use crate::daemon::state::DaemonState;
use crate::error::{ExitStatus, Result};

/// Handle daemon commands
pub async fn handle_daemon(cmd: DaemonCommand, global: &GlobalArgs) -> Result<ExitStatus> {
    match cmd {
        DaemonCommand::Start(args) => handle_start(args, global).await,
        DaemonCommand::Stop => handle_stop(global).await,
        DaemonCommand::Restart => handle_restart(global).await,
        DaemonCommand::Status => handle_status(global).await,
        DaemonCommand::Logs(args) => handle_logs(args, global).await,
        DaemonCommand::Install(args) => handle_install(args, global).await,
        DaemonCommand::Uninstall => handle_uninstall(global).await,
        DaemonCommand::Run => handle_run(global).await,
    }
}

async fn handle_start(args: DaemonStartArgs, global: &GlobalArgs) -> Result<ExitStatus> {
    let info = start_daemon(args.mcp, args.sync, args.port)?;

    let writer = OutputWriter::new(global);
    writer.write_value(&info)?;

    if !global.quiet {
        eprintln!("Daemon started with PID {}", info.pid.unwrap_or(0));
        eprintln!("Log file: {}", DaemonState::log_file_path()?.display());
    }

    Ok(ExitStatus::Success)
}

async fn handle_stop(global: &GlobalArgs) -> Result<ExitStatus> {
    let info = stop_daemon()?;

    let writer = OutputWriter::new(global);
    writer.write_value(&info)?;

    if !global.quiet {
        eprintln!("Daemon stopped");
    }

    Ok(ExitStatus::Success)
}

async fn handle_restart(global: &GlobalArgs) -> Result<ExitStatus> {
    let info = restart_daemon()?;

    let writer = OutputWriter::new(global);
    writer.write_value(&info)?;

    if !global.quiet {
        eprintln!("Daemon restarted with PID {}", info.pid.unwrap_or(0));
    }

    Ok(ExitStatus::Success)
}

async fn handle_status(global: &GlobalArgs) -> Result<ExitStatus> {
    let info = get_daemon_status()?;

    let writer = OutputWriter::new(global);
    writer.write_value(&info)?;

    if !global.quiet {
        if info.running {
            eprintln!("Daemon is running");
            eprintln!("  PID: {}", info.pid.unwrap_or(0));
            if let Some(uptime) = info.uptime_secs {
                eprintln!("  Uptime: {}s", uptime);
            }
            if let Some(ref last) = info.last_sync {
                eprintln!("  Last sync: {}", last);
            }
            if let Some(ref next) = info.next_sync {
                eprintln!("  Next sync: {}", next);
            }
            eprintln!("  Total syncs: {}", info.sync_status.total_syncs);
            if info.mcp_enabled {
                eprintln!("  MCP server: enabled (port {})", info.mcp_port);
            }
        } else {
            eprintln!("Daemon is not running");
        }
    }

    Ok(ExitStatus::Success)
}

async fn handle_logs(args: DaemonLogsArgs, global: &GlobalArgs) -> Result<ExitStatus> {
    let logs = read_daemon_logs(args.tail, args.follow)?;

    if global.effective_output_format() == crate::cli::args::OutputFormat::Json {
        let writer = OutputWriter::new(global);
        writer.write_value(&serde_json::json!({ "logs": logs }))?;
    } else {
        println!("{}", logs);
    }

    Ok(ExitStatus::Success)
}

async fn handle_install(args: DaemonInstallArgs, global: &GlobalArgs) -> Result<ExitStatus> {
    let service_type = args.service_type.unwrap_or_else(|| {
        #[cfg(target_os = "macos")]
        {
            ServiceType::Launchd
        }
        #[cfg(not(target_os = "macos"))]
        {
            ServiceType::Systemd
        }
    });

    let (content, path, instructions) = match service_type {
        ServiceType::Systemd => {
            let content = generate_systemd_service();
            let path = "/etc/systemd/system/gdelt.service";
            let instructions = format!(
                r#"To install the systemd service:

1. Save the service file:
   sudo tee {} > /dev/null << 'EOF'
{}EOF

2. Enable and start the service:
   sudo systemctl daemon-reload
   sudo systemctl enable gdelt
   sudo systemctl start gdelt

3. Check status:
   sudo systemctl status gdelt
"#,
                path, content
            );
            (content, path, instructions)
        }
        ServiceType::Launchd => {
            let content = generate_launchd_plist();
            let path = "~/Library/LaunchAgents/com.gdelt.daemon.plist";
            let instructions = format!(
                r#"To install the launchd service:

1. Save the plist file:
   cat > {} << 'EOF'
{}EOF

2. Load the service:
   launchctl load {}

3. Check status:
   launchctl list | grep gdelt
"#,
                path, content, path
            );
            (content, path, instructions)
        }
    };

    if global.effective_output_format() == crate::cli::args::OutputFormat::Json {
        let writer = OutputWriter::new(global);
        writer.write_value(&serde_json::json!({
            "service_type": format!("{:?}", service_type),
            "path": path,
            "content": content,
        }))?;
    } else {
        println!("{}", instructions);
    }

    Ok(ExitStatus::Success)
}

async fn handle_uninstall(global: &GlobalArgs) -> Result<ExitStatus> {
    let instructions = r#"To uninstall the daemon service:

For systemd:
   sudo systemctl stop gdelt
   sudo systemctl disable gdelt
   sudo rm /etc/systemd/system/gdelt.service
   sudo systemctl daemon-reload

For launchd:
   launchctl unload ~/Library/LaunchAgents/com.gdelt.daemon.plist
   rm ~/Library/LaunchAgents/com.gdelt.daemon.plist
"#;

    if global.effective_output_format() == crate::cli::args::OutputFormat::Json {
        let writer = OutputWriter::new(global);
        writer.write_value(&serde_json::json!({
            "instructions": instructions,
        }))?;
    } else {
        println!("{}", instructions);
    }

    Ok(ExitStatus::Success)
}

/// Run the daemon in the foreground (called by daemon start)
async fn handle_run(global: &GlobalArgs) -> Result<ExitStatus> {
    use crate::daemon::scheduler::{ScheduledTask, Scheduler};
    use crate::daemon::state::DaemonState;
    use crate::daemon::sync::run_sync;
    use tokio::time::{interval, Duration};

    if !global.quiet {
        eprintln!("Starting GDELT daemon in foreground mode");
    }

    let mut state = DaemonState::load()?;
    state.mark_started(std::process::id());
    state.save()?;

    // Set up scheduler
    let mut scheduler = Scheduler::new();

    // Add sync task (every 15 minutes - GDELT update frequency)
    scheduler.add_task(ScheduledTask::interval("sync", state.sync_interval_secs));

    // Main loop
    let mut check_interval = interval(Duration::from_secs(60));

    loop {
        check_interval.tick().await;

        // Collect due task names first to avoid borrow issues
        let due_task_names: Vec<String> = scheduler
            .due_tasks()
            .iter()
            .map(|t| t.name.clone())
            .collect();

        for task_name in due_task_names {
            match task_name.as_str() {
                "sync" => {
                    eprintln!("Running scheduled sync...");
                    match run_sync().await {
                        Ok(result) => {
                            let mut state = DaemonState::load()?;
                            state.record_sync_success(
                                result.files_downloaded,
                                result.events_imported,
                                result.gkg_imported,
                            );
                            state.save()?;
                            eprintln!(
                                "Sync complete: {} files, {} events",
                                result.files_downloaded, result.events_imported
                            );
                        }
                        Err(e) => {
                            let mut state = DaemonState::load()?;
                            state.record_sync_failure(&e.to_string());
                            state.save()?;
                            eprintln!("Sync failed: {}", e);
                        }
                    }
                    scheduler.mark_task_run("sync");
                }
                _ => {}
            }
        }

        // Update state
        let state = DaemonState::load()?;
        if !state.running {
            eprintln!("Daemon stop requested, shutting down...");
            break;
        }
    }

    // Cleanup
    let mut state = DaemonState::load()?;
    state.mark_stopped();
    state.save()?;

    Ok(ExitStatus::Success)
}