mod config;
mod eviction;
mod ipc;
mod node;
mod trust;

use std::sync::Arc;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use tracing::info;

#[derive(Parser)]
#[command(name = "irohds-daemon", about = "Decentralized memoization daemon")]
struct Cli {
    #[command(subcommand)]
    command: Option<Command>,
}

#[derive(Subcommand)]
enum Command {
    /// Run the daemon (default).
    Run,
    /// Install as a system service (systemd/launchd/schtasks).
    InstallService,
    /// Uninstall the system service.
    UninstallService,
    /// Print node info and exit.
    Info,
}

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| "irohds=info,iroh=warn".into()),
        )
        .init();

    let cli = Cli::parse();

    match cli.command.unwrap_or(Command::Run) {
        Command::Run => run().await,
        Command::InstallService => install_service(),
        Command::UninstallService => uninstall_service(),
        Command::Info => print_info().await,
    }
}

async fn run() -> Result<()> {
    config::ensure_dirs()?;
    let data_dir = config::data_dir();

    // Relay ON by default (for NAT traversal). Disable with IROHDS_NO_RELAY=1
    let no_relay = std::env::var("IROHDS_NO_RELAY").unwrap_or_default() == "1";

    let node = Arc::new(
        node::MemoNode::start(data_dir, no_relay)
            .await
            .context("start iroh node")?,
    );

    println!("IROHDS_NODE_ID={}", node.node_id());
    println!("IROHDS_NODE_ID_HEX={}", node.node_id_hex());

    // Periodic eviction
    {
        let node = node.clone();
        tokio::spawn(async move {
            let mut interval = tokio::time::interval(std::time::Duration::from_secs(300));
            loop {
                interval.tick().await;
                for ns in node.namespace_names().await {
                    if let Err(e) = node.run_eviction(&ns).await {
                        tracing::warn!(ns = %ns, err = %e, "eviction failed");
                    }
                }
            }
        });
    }

    // Graceful shutdown: persist eviction state, then exit
    let node_shutdown = node.clone();
    tokio::spawn(async move {
        let _ = tokio::signal::ctrl_c().await;
        info!("shutting down");
        node_shutdown.shutdown().await;
        std::process::exit(0);
    });

    ipc::serve(node).await
}

// -- Service installation ---------------------------------------------------

fn install_service() -> Result<()> {
    let exe = std::env::current_exe()?.to_string_lossy().to_string();
    let data = config::data_dir();
    let runtime = config::runtime_dir();

    // Ensure data dir exists before service starts
    std::fs::create_dir_all(&data)?;

    #[cfg(target_os = "linux")]
    {
        let home = dirs::home_dir().context("no home dir")?;
        let dir = home.join(".config/systemd/user");
        std::fs::create_dir_all(&dir)?;

        // systemd native sandboxing: daemon can only write to data + runtime dirs
        let unit = format!(
            "[Unit]\n\
             Description=irohds decentralized cache daemon\n\
             After=network-online.target\n\n\
             [Service]\n\
             Type=simple\n\
             ExecStart={exe}\n\
             Restart=on-failure\n\
             RestartSec=5\n\
             Environment=RUST_LOG=irohds=info,iroh=warn\n\
             # Sandbox: isolate host from iroh network\n\
             ProtectSystem=strict\n\
             ReadWritePaths={data} {runtime}\n\
             PrivateDevices=yes\n\
             NoNewPrivileges=yes\n\
             ProtectKernelTunables=yes\n\
             ProtectControlGroups=yes\n\n\
             [Install]\n\
             WantedBy=default.target\n",
            data = data.display(),
            runtime = runtime.display(),
        );
        let path = dir.join("irohds.service");
        std::fs::write(&path, unit)?;

        let _ = std::process::Command::new("systemctl")
            .args(["--user", "daemon-reload"])
            .status();
        let _ = std::process::Command::new("systemctl")
            .args(["--user", "enable", "--now", "irohds"])
            .status();

        // Enable lingering so service survives logout and starts at boot
        let _ = std::process::Command::new("loginctl")
            .args(["enable-linger"])
            .status();

        info!("installed systemd user service: {}", path.display());
    }

    #[cfg(target_os = "macos")]
    {
        let home = dirs::home_dir().context("no home dir")?;
        let dir = home.join("Library/LaunchAgents");
        std::fs::create_dir_all(&dir)?;
        let plist = format!(
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
             <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n\
               \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
             <plist version=\"1.0\">\n\
             <dict>\n\
               <key>Label</key>\n\
               <string>computer.iroh.irohds</string>\n\
               <key>ProgramArguments</key>\n\
               <array><string>{exe}</string></array>\n\
               <key>RunAtLoad</key>\n\
               <true/>\n\
               <key>KeepAlive</key>\n\
               <true/>\n\
               <key>StandardOutPath</key>\n\
               <string>{home}/Library/Logs/irohds.log</string>\n\
               <key>StandardErrorPath</key>\n\
               <string>{home}/Library/Logs/irohds.log</string>\n\
             </dict>\n\
             </plist>\n",
            home = home.display()
        );
        let path = dir.join("computer.iroh.irohds.plist");
        std::fs::write(&path, &plist)?;

        let _ = std::process::Command::new("launchctl")
            .args(["load", "-w", &path.to_string_lossy()])
            .status();

        info!("installed launchd service: {}", path.display());
    }

    #[cfg(windows)]
    {
        let status = std::process::Command::new("schtasks")
            .args([
                "/create", "/tn", "irohds",
                "/tr", &exe, "/sc", "onlogon",
                "/rl", "limited", "/f",
            ])
            .status()?;
        if !status.success() {
            anyhow::bail!("schtasks failed");
        }
        let _ = std::process::Command::new("schtasks")
            .args(["/run", "/tn", "irohds"])
            .status();

        info!("installed Windows scheduled task: irohds");
    }

    Ok(())
}

fn uninstall_service() -> Result<()> {
    #[cfg(target_os = "linux")]
    {
        let _ = std::process::Command::new("systemctl")
            .args(["--user", "disable", "--now", "irohds"])
            .status();
        let home = dirs::home_dir().context("no home")?;
        let path = home.join(".config/systemd/user/irohds.service");
        let _ = std::fs::remove_file(&path);
        println!("Removed: {}", path.display());
    }

    #[cfg(target_os = "macos")]
    {
        let home = dirs::home_dir().context("no home")?;
        let path = home.join("Library/LaunchAgents/computer.iroh.irohds.plist");
        let _ = std::process::Command::new("launchctl")
            .args(["unload", &path.to_string_lossy()])
            .status();
        let _ = std::fs::remove_file(&path);
        println!("Removed: {}", path.display());
    }

    #[cfg(windows)]
    {
        let _ = std::process::Command::new("schtasks")
            .args(["/delete", "/tn", "irohds", "/f"])
            .status();
        println!("Removed Windows scheduled task: irohds");
    }

    Ok(())
}

async fn print_info() -> Result<()> {
    config::ensure_dirs()?;
    println!("Data dir:    {}", config::data_dir().display());
    println!("Socket:      {}", config::socket_name());
    println!("Runtime dir: {}", config::runtime_dir().display());
    Ok(())
}
