use askama::Template;
use askama_web::WebTemplate;
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::response::{AppendHeaders, IntoResponse, Redirect, Response};
use axum::Form;
use kube::api::{DeleteParams, ListParams, PostParams};
use kube::Api;
use serde::Deserialize;

use crate::auth_guard::AuthUser;
use crate::flash;
use crate::htmx;
use crate::k8s::{ErgoNetwork, default_network_spec, sanitize_name};
use crate::page::PageContext;
use crate::state::AppState;

#[derive(Template, WebTemplate)]
#[template(path = "networks.html")]
pub struct NetworksTemplate {
    pub page: PageContext,
    pub networks: Vec<NetworkInfo>,
}

pub struct NetworkInfo {
    pub name: String,
    pub hostname: String,
    pub network_name: String,
    pub ready: bool,
}

#[derive(Template, WebTemplate)]
#[template(path = "network_detail.html")]
pub struct NetworkDetailTemplate {
    pub page: PageContext,
    pub name: String,
    pub hostname: String,
    pub network_name: String,
    pub ready: bool,
    pub status_message: String,
}

#[derive(Deserialize)]
pub struct CreateForm {
    pub name: String,
    #[serde(default)]
    pub title: Option<String>,
}

#[derive(Template)]
#[template(path = "partials/network_creating.html")]
struct NetworkCreatingFragment {
    name: String,
}

#[derive(Template)]
#[template(path = "partials/flash_fragment.html")]
struct FlashFragment {
    css_class: &'static str,
    text: &'static str,
}

#[derive(Template)]
#[template(path = "partials/network_status_fragment.html")]
struct NetworkStatusFragment {
    ready: bool,
}

pub async fn list(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
) -> impl IntoResponse {
    let api: Api<ErgoNetwork> = Api::namespaced(state.kube.clone(), &state.namespace);
    let lp = ListParams::default().labels(&format!("irc.now/owner={}", user.sub));

    let networks = api
        .list(&lp)
        .await
        .map(|list| {
            list.items
                .into_iter()
                .map(|n| {
                    let ready = n.status.as_ref()
                        .and_then(|s| s.conditions.first())
                        .map(|c| c.status == "True")
                        .unwrap_or(false);
                    NetworkInfo {
                        name: n.metadata.name.unwrap_or_default(),
                        hostname: n.spec.hostname,
                        network_name: n.spec.network_name.unwrap_or_default(),
                        ready,
                    }
                })
                .collect()
        })
        .unwrap_or_default();

    let page = PageContext::from_request(&headers, &state.announcement);
    let clear = page.flash.is_some();

    let template = NetworksTemplate { page, networks };

    if clear {
        (AppendHeaders([flash::clear_flash()]), template).into_response()
    } else {
        template.into_response()
    }
}

pub async fn create(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
    Form(form): Form<CreateForm>,
) -> Response {
    if !user.is_pro() {
        if htmx::is_htmx(&headers) {
            return axum::http::Response::builder()
                .status(403)
                .body(axum::body::Body::from(
                    FlashFragment { css_class: "flash-error", text: "upgrade to pro to create a network" }
                        .render().unwrap_or_default()
                ))
                .unwrap()
                .into_response();
        }
        return axum::http::StatusCode::FORBIDDEN.into_response();
    }

    let name = sanitize_name(&form.name);
    if name.is_empty() {
        if htmx::is_htmx(&headers) {
            return axum::http::Response::builder()
                .status(200)
                .body(axum::body::Body::from(
                    FlashFragment { css_class: "flash-error", text: "failed to create network." }
                        .render().unwrap_or_default()
                ))
                .unwrap()
                .into_response();
        }
        return (AppendHeaders([flash::set_flash("network_create_failed")]), Redirect::to("/networks")).into_response();
    }

    let user_sub = user.sub.clone();

    let mut spec = default_network_spec(&name);
    if let Some(title) = form.title.filter(|t| !t.trim().is_empty()) {
        spec.network_name = Some(title.trim().to_string());
    }

    let mut network = ErgoNetwork::new(&name, spec);
    network
        .metadata
        .labels
        .get_or_insert_with(Default::default)
        .insert("irc.now/owner".to_string(), user.sub);

    let api: Api<ErgoNetwork> = Api::namespaced(state.kube.clone(), &state.namespace);
    if let Err(e) = api.create(&PostParams::default(), &network).await {
        let flash_code = match &e {
            kube::Error::Api(status) if status.is_already_exists() => "network_name_taken",
            _ => {
                tracing::error!("failed to create network: {e}");
                "network_create_failed"
            }
        };

        if htmx::is_htmx(&headers) {
            let msg = flash::resolve(flash_code).unwrap();
            return axum::http::Response::builder()
                .status(200)
                .body(axum::body::Body::from(
                    FlashFragment { css_class: msg.css_class(), text: msg.text }
                        .render().unwrap_or_default()
                ))
                .unwrap()
                .into_response();
        }
        return (AppendHeaders([flash::set_flash(flash_code)]), Redirect::to("/networks")).into_response();
    }

    crate::events::record(
        &state.db, &user_sub, "network_create",
        Some(serde_json::json!({"network": name})),
    ).await;

    if htmx::is_htmx(&headers) {
        let fragment = NetworkCreatingFragment { name };
        axum::http::Response::builder()
            .status(200)
            .body(axum::body::Body::from(fragment.render().unwrap_or_default()))
            .unwrap()
            .into_response()
    } else {
        (AppendHeaders([flash::set_flash("network_created")]), Redirect::to("/networks")).into_response()
    }
}

pub async fn detail(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
    Path(name): Path<String>,
) -> Result<impl IntoResponse, impl IntoResponse> {
    let api: Api<ErgoNetwork> = Api::namespaced(state.kube.clone(), &state.namespace);

    let network = api.get(&name).await.map_err(|e| {
        tracing::error!("failed to get network {name}: {e}");
        (AppendHeaders([flash::set_flash("network_not_found")]), Redirect::to("/networks"))
    })?;

    let owner = network.metadata.labels.as_ref()
        .and_then(|l| l.get("irc.now/owner"))
        .map(|s| s.as_str());
    if owner != Some(&user.sub) {
        return Err((AppendHeaders([flash::set_flash("network_not_found")]), Redirect::to("/networks")));
    }

    let (ready, status_message) = network.status.as_ref()
        .and_then(|s| s.conditions.first())
        .map(|c| (
            c.status == "True",
            c.message.clone().unwrap_or_default(),
        ))
        .unwrap_or((false, "pending".to_string()));

    let page = PageContext::from_request(&headers, &state.announcement);
    let clear = page.flash.is_some();

    let template = NetworkDetailTemplate {
        page,
        name,
        hostname: network.spec.hostname,
        network_name: network.spec.network_name.unwrap_or_default(),
        ready,
        status_message,
    };

    if clear {
        Ok((AppendHeaders([flash::clear_flash()]), template).into_response())
    } else {
        Ok(template.into_response())
    }
}

pub async fn delete(
    State(state): State<AppState>,
    headers: HeaderMap,
    AuthUser(user): AuthUser,
    Path(name): Path<String>,
) -> Response {
    let api: Api<ErgoNetwork> = Api::namespaced(state.kube.clone(), &state.namespace);

    match api.get(&name).await {
        Ok(network) => {
            let owner = network.metadata.labels.as_ref()
                .and_then(|l| l.get("irc.now/owner"))
                .map(|s| s.as_str());
            if owner != Some(&user.sub) {
                if htmx::is_htmx(&headers) {
                    return ([htmx::hx_redirect("/networks")], "").into_response();
                }
                return (AppendHeaders([flash::set_flash("network_not_found")]), Redirect::to("/networks")).into_response();
            }
        }
        Err(_) => {
            if htmx::is_htmx(&headers) {
                return ([htmx::hx_redirect("/networks")], "").into_response();
            }
            return (AppendHeaders([flash::set_flash("network_not_found")]), Redirect::to("/networks")).into_response();
        }
    }

    match api.delete(&name, &DeleteParams::default()).await {
        Ok(_) => {
            crate::events::record(
                &state.db, &user.sub, "network_delete",
                Some(serde_json::json!({"network": name})),
            ).await;
            if htmx::is_htmx(&headers) {
                ([htmx::hx_redirect("/networks")], "").into_response()
            } else {
                (AppendHeaders([flash::set_flash("network_deleted")]), Redirect::to("/networks")).into_response()
            }
        }
        Err(e) => {
            tracing::error!("failed to delete network {name}: {e}");
            if htmx::is_htmx(&headers) {
                ([htmx::hx_redirect(&format!("/networks/{name}"))], "").into_response()
            } else {
                (AppendHeaders([flash::set_flash("network_delete_failed")]), Redirect::to(&format!("/networks/{name}"))).into_response()
            }
        }
    }
}

pub async fn status(
    State(state): State<AppState>,
    AuthUser(user): AuthUser,
    Path(name): Path<String>,
) -> Response {
    let api: Api<ErgoNetwork> = Api::namespaced(state.kube.clone(), &state.namespace);

    let network = match api.get(&name).await {
        Ok(n) => n,
        Err(_) => return axum::http::StatusCode::NOT_FOUND.into_response(),
    };

    let owner = network.metadata.labels.as_ref()
        .and_then(|l| l.get("irc.now/owner"))
        .map(|s| s.as_str());
    if owner != Some(&user.sub) {
        return axum::http::StatusCode::NOT_FOUND.into_response();
    }

    let ready = network.status.as_ref()
        .and_then(|s| s.conditions.first())
        .map(|c| c.status == "True")
        .unwrap_or(false);

    let fragment = NetworkStatusFragment { ready };
    let html = fragment.render().unwrap_or_default();

    if ready {
        ([("hx-trigger", "network-ready")], axum::response::Html(html)).into_response()
    } else {
        axum::response::Html(html).into_response()
    }
}
