use askama::Template;
use askama_web::WebTemplate;
use axum::extract::State;
use axum::response::Redirect;
use axum::Form;
use k8s_openapi::api::core::v1::Secret;
use kube::api::{ListParams, PostParams};
use kube::Api;
use serde::Deserialize;

use crate::auth_guard::AuthUser;
use crate::k8s::{SojuBouncer, SojuBouncerSpec, sanitize_bouncer_name};
use crate::state::AppState;

#[derive(Template, WebTemplate)]
#[template(path = "bouncers.html")]
pub struct BouncersTemplate {
    pub bouncers: Vec<BouncerInfo>,
}

pub struct BouncerInfo {
    pub name: String,
    pub hostname: String,
}

pub async fn list(
    State(state): State<AppState>,
    AuthUser(user): AuthUser,
) -> BouncersTemplate {
    let api: Api<SojuBouncer> = Api::namespaced(state.kube, &state.namespace);
    let lp = ListParams::default().labels(&format!("irc.josie.cloud/owner={}", user.sub));

    let bouncers = api
        .list(&lp)
        .await
        .map(|list| {
            list.items
                .into_iter()
                .map(|b| BouncerInfo {
                    name: b.metadata.name.unwrap_or_default(),
                    hostname: b.spec.hostname,
                })
                .collect()
        })
        .unwrap_or_default();

    BouncersTemplate { bouncers }
}

#[derive(Deserialize)]
pub struct CreateForm {
    pub name: String,
}

pub async fn create(
    State(state): State<AppState>,
    AuthUser(user): AuthUser,
    Form(form): Form<CreateForm>,
) -> Result<Redirect, Redirect> {
    let name = sanitize_bouncer_name(&form.name);
    if name.is_empty() {
        return Err(Redirect::to("/bouncers"));
    }

    let bouncer = SojuBouncer::new(&name, SojuBouncerSpec {
        hostname: format!("{name}.irc.now"),
        owner: user.sub.clone(),
        hostname_cloak: Some(format!("{name}.irc.now")),
    });

    let mut bouncer = bouncer;
    bouncer
        .metadata
        .labels
        .get_or_insert_with(Default::default)
        .insert(
            "irc.josie.cloud/owner".to_string(),
            user.sub,
        );

    let api: Api<SojuBouncer> = Api::namespaced(state.kube.clone(), &state.namespace);
    api.create(&PostParams::default(), &bouncer)
        .await
        .map_err(|e| {
            tracing::error!("failed to create bouncer: {e}");
            Redirect::to("/bouncers")
        })?;

    if let Err(e) = add_default_upstream(&state, &name).await {
        tracing::warn!("failed to add default upstream: {e}");
    }

    Ok(Redirect::to("/bouncers"))
}

async fn add_default_upstream(
    state: &AppState,
    bouncer_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    let secret_api: Api<Secret> = Api::namespaced(state.kube.clone(), &state.namespace);
    let secret = secret_api.get(&format!("{bouncer_name}-db")).await?;

    let uri = secret
        .data
        .as_ref()
        .and_then(|d| d.get("uri"))
        .map(|b| String::from_utf8_lossy(&b.0).to_string())
        .ok_or("missing uri in db secret")?;

    let (client, connection) = tokio_postgres::connect(&uri, tokio_postgres::NoTls).await?;
    tokio::spawn(async move {
        if let Err(e) = connection.await {
            tracing::warn!("soju db connection error: {e}");
        }
    });

    client
        .execute(
            r#"INSERT INTO "Network" ("user", name, addr, nick, enabled, connect_commands)
               SELECT id, 'irc.now', 'irc-now-net:6697', username, true, ''
               FROM "User"
               LIMIT 1
               ON CONFLICT DO NOTHING"#,
            &[],
        )
        .await?;

    Ok(())
}
