use anyhow::{bail, Result};

use crate::config::Config;
use crate::container::ContainerManager;
use crate::spt::server::SptClient;

use super::common::CliContext;
use super::ServerAction;

pub async fn run(action: &ServerAction, ctx: &CliContext) -> Result<()> {
    match action {
        ServerAction::Start { timeout } => start(ctx, *timeout).await,
        ServerAction::Stop => stop(ctx).await,
        ServerAction::Restart { drain, skip_queue } => restart(ctx, *drain, *skip_queue).await,
        ServerAction::Logs { follow } => logs(ctx, *follow).await,
        ServerAction::Status { json } => crate::cli::status::run(*json, ctx).await,
        ServerAction::Create { .. } => unreachable!("handled in main.rs"),
    }
}

async fn start(ctx: &CliContext, timeout_secs: u64) -> Result<()> {
    let (mgr, container) = require_container(ctx)?;

    if ctx.config.auto_drain_on_lifecycle {
        drain_if_pending(ctx).await?;
    }

    println!("Starting SPT server container...");
    mgr.start(container).await?;

    wait_for_ping(ctx, timeout_secs).await
}

async fn stop(ctx: &CliContext) -> Result<()> {
    let (mgr, container) = require_container(ctx)?;

    println!("Stopping SPT server container...");
    mgr.stop(container).await?;
    println!("Server stopped.");

    if ctx.config.auto_drain_on_lifecycle {
        drain_if_pending(ctx).await?;
    }

    Ok(())
}

async fn restart(ctx: &CliContext, force_drain: bool, skip_queue: bool) -> Result<()> {
    let (mgr, container) = require_container(ctx)?;

    println!("Stopping SPT server container...");
    mgr.stop(container).await?;
    println!("Server stopped.");

    let should_drain = if skip_queue {
        false
    } else if force_drain {
        true
    } else {
        ctx.config.auto_drain_on_lifecycle
    };

    if should_drain {
        drain_if_pending(ctx).await?;
    }

    println!("Starting SPT server container...");
    mgr.start(container).await?;

    wait_for_ping(ctx, 60).await
}

async fn logs(ctx: &CliContext, follow: bool) -> Result<()> {
    let (mgr, container) = require_container(ctx)?;
    use futures_util::StreamExt;
    let mut stream = mgr.log_stream(container, 100, follow);
    while let Some(log) = stream.next().await {
        match log? {
            bollard::container::LogOutput::StdOut { message } => {
                print!("{}", String::from_utf8_lossy(&message));
            }
            bollard::container::LogOutput::StdErr { message } => {
                eprint!("{}", String::from_utf8_lossy(&message));
            }
            _ => {}
        }
    }
    Ok(())
}

pub async fn create_container(name: &str, port: u16, cli: &super::Cli) -> Result<()> {
    let spt_dir = cli
        .spt_dir
        .clone()
        .or_else(|| std::env::current_dir().ok())
        .unwrap_or_else(|| std::path::PathBuf::from("."));

    println!("Pulling {}...", crate::container::SPT_SERVER_IMAGE);
    let mgr = ContainerManager::new()?;
    mgr.pull_image(crate::container::SPT_SERVER_IMAGE).await?;

    println!("Creating container '{name}'...");
    mgr.create_container(crate::container::CreateContainerOpts {
        name: name.to_string(),
        image: crate::container::SPT_SERVER_IMAGE.to_string(),
        env: vec![
            ("TAKE_OWNERSHIP".to_string(), "true".to_string()),
            ("CHANGE_PERMISSIONS".to_string(), "true".to_string()),
            ("LISTEN_ALL_NETWORKS".to_string(), "true".to_string()),
        ],
        volumes: vec![crate::container::VolumeMount {
            host_path: spt_dir.clone(),
            container_path: "/opt/tarkov".to_string(),
            read_only: false,
            selinux: crate::container::SelinuxLabel::Private,
        }],
        ports: vec![crate::container::PortMapping {
            host_port: port,
            container_port: crate::container::DEFAULT_SPT_PORT,
            protocol: crate::container::Protocol::Tcp,
        }],
        labels: vec![("app".to_string(), "spt-server".to_string())],
        user: Some("root".to_string()),
    })
    .await?;
    println!("Container '{name}' created successfully.");

    let config_path = Config::resolve_path(cli.config.as_deref(), Some(&spt_dir));
    let mut config = if config_path.exists() {
        Config::load(&config_path)?
    } else {
        Config::default()
    };

    if config.server_container.is_none() {
        config.server_container = Some(name.to_string());
        config.save(&config_path)?;
        println!("Updated config: server_container = {name}");
    }

    Ok(())
}

async fn wait_for_ping(ctx: &CliContext, timeout_secs: u64) -> Result<()> {
    let (host, port) = crate::server_detect::resolve_server_addr(&ctx.config, &ctx.spt_dir);
    let spt_client = SptClient::new(&host, port)?;

    println!("Waiting for server to respond (timeout: {timeout_secs}s)...");
    let start_time = std::time::Instant::now();
    let timeout = std::time::Duration::from_secs(timeout_secs);

    loop {
        if start_time.elapsed() > timeout {
            bail!(
                "Server did not respond within {timeout_secs}s — check `quma server logs` for errors"
            );
        }

        let ping = spt_client.ping().await?;
        if ping.ok {
            println!("Server is ready (responded in {}ms).", ping.latency_ms);
            return Ok(());
        }

        tokio::time::sleep(std::time::Duration::from_secs(2)).await;
    }
}

fn require_container(ctx: &CliContext) -> Result<(&ContainerManager, &str)> {
    match (&ctx.config.server_container, &ctx.container_mgr) {
        (None, _) => bail!(
            "no server_container configured.\n\
             Run `quma server create` to create one, or\n\
             set it with: quma config set server_container <name>"
        ),
        (Some(_), None) => bail!(
            "failed to connect to Podman socket.\n\
             Ensure podman.socket is enabled:\n  \
             systemctl --user enable --now podman.socket"
        ),
        (Some(name), Some(mgr)) => Ok((mgr, name.as_str())),
    }
}

async fn drain_if_pending(ctx: &CliContext) -> Result<()> {
    let pending = ctx.db.list_pending_ops()?;
    if pending.is_empty() {
        return Ok(());
    }
    println!("\nDraining {} pending operation(s)...", pending.len());
    crate::cli::apply::drain_all(ctx).await?;
    Ok(())
}
