//! `fafb-q drain` command — trigger and monitor graceful drain.
//!
//! Uses the HTTP admin endpoints:
//! - `POST /admin/drain` — trigger drain
//! - `GET /admin/drain/status` — check drain progress

use colored::Colorize;

use super::{
    CliConfig, CliError, DrainArgs, OutputFormat, build_http_client, extract_error_message,
    http_base_url, response_json,
};

pub async fn run(config: CliConfig, args: DrainArgs) -> Result<(), CliError> {
    let admin_key = args.admin_key.ok_or_else(|| {
        CliError::Error(
            "admin API key required — use --admin-key or FAFB_ADMIN_API_KEY env var".into(),
        )
    })?;

    let format = OutputFormat::auto_detect(config.json, false);
    let client = build_http_client(&config)?;
    let base = http_base_url(&config);

    if args.status {
        drain_status(&client, &base, &admin_key, format).await
    } else {
        trigger_drain(
            &client,
            &base,
            &admin_key,
            args.reason,
            args.timeout,
            args.wait,
            format,
            config.timeout_secs,
        )
        .await
    }
}

#[allow(clippy::too_many_arguments)]
async fn trigger_drain(
    client: &reqwest::Client,
    base: &str,
    admin_key: &str,
    reason: Option<String>,
    timeout: Option<u64>,
    wait: bool,
    format: OutputFormat,
    cli_timeout_secs: u64,
) -> Result<(), CliError> {
    let url = format!("{base}/admin/drain");

    let mut body = serde_json::Map::new();
    if let Some(reason) = reason {
        body.insert("reason".into(), serde_json::Value::String(reason));
    }
    if let Some(timeout_secs) = timeout {
        body.insert(
            "timeout_secs".into(),
            serde_json::Value::Number(timeout_secs.into()),
        );
    }

    let resp = client
        .post(&url)
        .bearer_auth(admin_key)
        .json(&body)
        .send()
        .await
        .map_err(|e| CliError::Error(format!("HTTP request failed: {e}")))?;

    let status = resp.status();
    let resp_body = response_json(resp).await?;

    if status == reqwest::StatusCode::CONFLICT {
        let msg = resp_body
            .get("message")
            .and_then(|v| v.as_str())
            .unwrap_or("server is already draining");
        return Err(CliError::Error(msg.to_string()));
    }

    if status == reqwest::StatusCode::FORBIDDEN {
        return Err(CliError::Error("forbidden — check admin API key".into()));
    }

    if !status.is_success() {
        return Err(CliError::Error(format!(
            "drain failed ({status}): {}",
            extract_error_message(&resp_body)
        )));
    }

    match format {
        OutputFormat::Json if !wait => {
            println!("{resp_body}");
        }
        _ if !wait => {
            let active = resp_body
                .get("active_connections")
                .and_then(|v| v.as_u64())
                .unwrap_or(0);
            let deadline = resp_body
                .get("deadline")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown");
            println!("{}", "Drain initiated.".green());
            println!("  {} {active}", "Active connections:".dimmed());
            println!("  {} {deadline}", "Deadline:          ".dimmed());
        }
        _ => {
            // --wait mode: print initial status, then poll.
            let active = resp_body
                .get("active_connections")
                .and_then(|v| v.as_u64())
                .unwrap_or(0);
            if format != OutputFormat::Json {
                eprintln!("Drain initiated with {active} active connections. Waiting...");
            }
        }
    }

    if wait {
        poll_drain_status(client, base, admin_key, format, cli_timeout_secs).await?;
    }

    Ok(())
}

/// Poll `/admin/drain/status` every second until the server is no longer draining
/// or the CLI timeout is reached.
async fn poll_drain_status(
    client: &reqwest::Client,
    base: &str,
    admin_key: &str,
    format: OutputFormat,
    timeout_secs: u64,
) -> Result<(), CliError> {
    let deadline =
        tokio::time::Instant::now() + std::time::Duration::from_secs(timeout_secs.max(5));
    let url = format!("{base}/admin/drain/status");

    loop {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;

        if tokio::time::Instant::now() >= deadline {
            return Err(CliError::Timeout(
                "timed out waiting for drain to complete".into(),
            ));
        }

        let resp = client
            .get(&url)
            .bearer_auth(admin_key)
            .send()
            .await
            .map_err(|e| {
                if e.is_connect() {
                    // Server shut down — drain is complete.
                    return CliError::Error(String::new());
                }
                CliError::Error(format!("status check failed: {e}"))
            });

        let resp = match resp {
            Ok(r) => r,
            Err(e) if e.exit_code() == 1 && format!("{e}").is_empty() => {
                // Server is gone — drain completed.
                if format != OutputFormat::Json {
                    println!("{}", "Server has shut down. Drain complete.".green());
                } else {
                    println!(
                        "{}",
                        serde_json::json!({"state": "shutting_down", "complete": true})
                    );
                }
                return Ok(());
            }
            Err(e) => return Err(e),
        };

        let body = response_json(resp).await?;
        let state = body
            .get("state")
            .and_then(|v| v.as_str())
            .unwrap_or("unknown");

        match state {
            "running" => {
                // Drain hasn't started or was cancelled.
                if format != OutputFormat::Json {
                    println!("Server state: running (drain may not have started)");
                }
                return Ok(());
            }
            "shutting_down" => {
                if format == OutputFormat::Json {
                    println!("{body}");
                } else {
                    println!("{}", "Drain complete. Server is shutting down.".green());
                }
                return Ok(());
            }
            _ => {
                // Still draining — report progress.
                let active = body
                    .get("active_connections")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(0);
                let drained = body
                    .get("connections_drained")
                    .and_then(|v| v.as_u64())
                    .unwrap_or(0);
                if format != OutputFormat::Json {
                    eprint!("\r  draining... {active} active, {drained} drained");
                }
            }
        }
    }
}

async fn drain_status(
    client: &reqwest::Client,
    base: &str,
    admin_key: &str,
    format: OutputFormat,
) -> Result<(), CliError> {
    let url = format!("{base}/admin/drain/status");

    let resp = client
        .get(&url)
        .bearer_auth(admin_key)
        .send()
        .await
        .map_err(|e| CliError::Error(format!("HTTP request failed: {e}")))?;

    let status = resp.status();
    let body = response_json(resp).await?;

    if status == reqwest::StatusCode::FORBIDDEN {
        return Err(CliError::Error("forbidden — check admin API key".into()));
    }

    if !status.is_success() {
        return Err(CliError::Error(format!(
            "drain status failed ({status}): {}",
            extract_error_message(&body)
        )));
    }

    match format {
        OutputFormat::Json => {
            println!("{body}");
        }
        _ => {
            let state = body
                .get("state")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown");
            let active = body
                .get("active_connections")
                .and_then(|v| v.as_u64())
                .unwrap_or(0);
            let drained = body
                .get("connections_drained")
                .and_then(|v| v.as_u64())
                .unwrap_or(0);

            let colored_state = match state {
                "running" => state.green().to_string(),
                "draining" => state.yellow().to_string(),
                "shutting_down" => state.red().to_string(),
                _ => state.to_string(),
            };
            println!("{} {colored_state}", "Server state:       ".dimmed());
            println!("{} {active}", "Active connections:  ".dimmed());
            println!("{} {drained}", "Connections drained: ".dimmed());

            if let Some(started) = body.get("drain_started_at").and_then(|v| v.as_str()) {
                println!("{} {started}", "Drain started at:   ".dimmed());
            }
            if let Some(deadline) = body.get("drain_deadline").and_then(|v| v.as_str()) {
                println!("{} {deadline}", "Drain deadline:     ".dimmed());
            }
        }
    }

    Ok(())
}
