use k8s_openapi::api::core::v1::Secret;
use kube::Api;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::TcpStream;

const ERGO_HOST: &str = "irc-now-net";
const ERGO_PORT: u16 = 6667;
const OPER_SECRET_NAME: &str = "irc-now-net-oper";
const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);

async fn read_oper_password(kube: &kube::Client, namespace: &str) -> Result<String, String> {
    let api: Api<Secret> = Api::namespaced(kube.clone(), namespace);
    let secret = api
        .get(OPER_SECRET_NAME)
        .await
        .map_err(|e| format!("failed to get oper secret: {e}"))?;

    secret
        .data
        .as_ref()
        .and_then(|d| d.get("password"))
        .map(|b| String::from_utf8_lossy(&b.0).trim().to_string())
        .ok_or_else(|| "missing 'password' key in oper secret".to_string())
}

pub async fn ensure_ergo_account(
    kube: &kube::Client,
    namespace: &str,
    username: &str,
    password: &str,
) -> Result<(), String> {
    let oper_password = read_oper_password(kube, namespace).await?;

    tokio::time::timeout(TIMEOUT, async {
        let stream = TcpStream::connect((ERGO_HOST, ERGO_PORT))
            .await
            .map_err(|e| format!("connect to ergo failed: {e}"))?;

        let (reader, mut writer) = stream.into_split();
        let mut lines = BufReader::new(reader).lines();

        writer
            .write_all(b"NICK ergo-admin\r\nUSER ergo-admin 0 * :admin\r\n")
            .await
            .map_err(|e| format!("write failed: {e}"))?;

        loop {
            let line = lines
                .next_line()
                .await
                .map_err(|e| format!("read failed: {e}"))?
                .ok_or("connection closed before welcome")?;

            if line.starts_with("PING") {
                let token = line.splitn(2, ' ').nth(1).unwrap_or("");
                writer
                    .write_all(format!("PONG {token}\r\n").as_bytes())
                    .await
                    .map_err(|e| format!("write failed: {e}"))?;
            }

            if line.contains(" 001 ") {
                break;
            }
        }

        writer
            .write_all(format!("OPER admin {oper_password}\r\n").as_bytes())
            .await
            .map_err(|e| format!("write failed: {e}"))?;

        loop {
            let line = lines
                .next_line()
                .await
                .map_err(|e| format!("read failed: {e}"))?
                .ok_or("connection closed before oper reply")?;

            if line.starts_with("PING") {
                let token = line.splitn(2, ' ').nth(1).unwrap_or("");
                writer
                    .write_all(format!("PONG {token}\r\n").as_bytes())
                    .await
                    .map_err(|e| format!("write failed: {e}"))?;
            }

            if line.contains(" 381 ") {
                break;
            }

            if line.contains(" 491 ") || line.contains("ERR_NOOPERHOST") {
                return Err("OPER authentication failed".to_string());
            }
        }

        writer
            .write_all(
                format!("NS SAREGISTER {username} {password}\r\n").as_bytes(),
            )
            .await
            .map_err(|e| format!("write failed: {e}"))?;

        loop {
            let line = lines
                .next_line()
                .await
                .map_err(|e| format!("read failed: {e}"))?
                .ok_or("connection closed before SAREGISTER reply")?;

            if line.starts_with("PING") {
                let token = line.splitn(2, ' ').nth(1).unwrap_or("");
                writer
                    .write_all(format!("PONG {token}\r\n").as_bytes())
                    .await
                    .map_err(|e| format!("write failed: {e}"))?;
            }

            if line.contains("NickServ") || line.contains("nickserv") {
                let lower = line.to_lowercase();

                if lower.contains("successfully registered") || lower.contains("registered account") {
                    tracing::info!("registered ergo account '{username}'");
                    let _ = writer.write_all(b"QUIT\r\n").await;
                    return Ok(());
                }

                if lower.contains("account already exists") {
                    tracing::info!("ergo account '{username}' exists, resetting password");
                    writer
                        .write_all(
                            format!("NS SAPASSWD {username} {password}\r\n").as_bytes(),
                        )
                        .await
                        .map_err(|e| format!("write failed: {e}"))?;

                    loop {
                        let line2 = lines
                            .next_line()
                            .await
                            .map_err(|e| format!("read failed: {e}"))?
                            .ok_or("connection closed before SAPASSWD reply")?;

                        if line2.starts_with("PING") {
                            let token = line2.splitn(2, ' ').nth(1).unwrap_or("");
                            writer
                                .write_all(format!("PONG {token}\r\n").as_bytes())
                                .await
                                .map_err(|e| format!("write failed: {e}"))?;
                        }

                        if line2.contains("NickServ") || line2.contains("nickserv") {
                            let lower2 = line2.to_lowercase();
                            let _ = writer.write_all(b"QUIT\r\n").await;

                            if lower2.contains("error") || lower2.contains("fail") || lower2.contains("unknown command") {
                                return Err(format!("SAPASSWD failed: {line2}"));
                            }

                            tracing::info!("reset ergo password for '{username}'");
                            return Ok(());
                        }
                    }
                }

                if lower.contains("error") || lower.contains("fail") {
                    let _ = writer.write_all(b"QUIT\r\n").await;
                    return Err(format!("SAREGISTER failed: {line}"));
                }

                let _ = writer.write_all(b"QUIT\r\n").await;
                tracing::info!("SAREGISTER response: {line}");
                return Ok(());
            }
        }
    })
    .await
    .map_err(|_| "ergo admin connection timed out".to_string())?
}

pub fn generate_password() -> String {
    use rand::Rng;
    const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    let mut rng = rand::thread_rng();
    (0..24)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect()
}
