pub mod context;
pub mod error;
pub mod handlers;
pub mod session;

use anyhow::{Context as _, Result};
use axum::{
    extract::Request,
    middleware::{Next, from_fn_with_state},
    response::{IntoResponse, Redirect, Response},
    routing::{get, post},
    Router,
};
use rust_embed::RustEmbed;
use std::path::Path;
use tera::Tera;

use crate::AppState;

// ── Embedded assets ───────────────────────────────────────────────────────────

#[derive(RustEmbed)]
#[folder = "ui/templates/"]
struct UiAssets;

#[derive(RustEmbed)]
#[folder = "ui/public/"]
struct PublicAssets;

// ── UiRenderer ────────────────────────────────────────────────────────────────

pub struct UiRenderer {
    tera: Tera,
}

impl UiRenderer {
    pub fn from_embedded() -> Result<Self> {
        let mut tera = Tera::default();
        let templates: Vec<(String, String)> = UiAssets::iter()
            .filter_map(|name| {
                let file = UiAssets::get(&name)?;
                let src = std::str::from_utf8(&file.data).ok()?.to_string();
                Some((name.into_owned(), src))
            })
            .collect();
        tera.add_raw_templates(templates.iter().map(|(n, s)| (n.as_str(), s.as_str())))
            .context("tera parse embedded ui templates")?;
        Ok(Self { tera })
    }

    pub fn from_dir(dir: &Path) -> Result<Self> {
        let pattern = format!("{}/**/*", dir.display());
        let tera = Tera::new(&pattern)
            .with_context(|| format!("tera parse {}", dir.display()))?;
        Ok(Self { tera })
    }

    pub fn render(&self, template: &str, ctx: &tera::Context) -> Result<String, tera::Error> {
        self.tera.render(template, ctx)
    }
}

// ── Static public asset handler ───────────────────────────────────────────────

async fn serve_public(axum::extract::Path(path): axum::extract::Path<String>) -> Response {
    match PublicAssets::get(&path) {
        Some(file) => {
            let mime = mime_guess::from_path(&path).first_or_octet_stream();
            (
                [(axum::http::header::CONTENT_TYPE, mime.as_ref())],
                file.data.into_owned(),
            )
                .into_response()
        }
        None => axum::http::StatusCode::NOT_FOUND.into_response(),
    }
}

// ── Session middleware ─────────────────────────────────────────────────────────

async fn session_guard(
    axum::extract::State(app): axum::extract::State<AppState>,
    mut req: Request,
    next: Next,
) -> Response {
    let session_id = extract_session_cookie(req.headers());
    let session_data = match session_id {
        Some(id) => app.sessions.get(id).await,
        None => None,
    };
    match session_data {
        Some(data) => {
            let perms = session::UiPerms::from_session(&data);
            req.extensions_mut().insert(perms);
            next.run(req).await
        }
        None => Redirect::to("/ui/login").into_response(),
    }
}

pub fn extract_session_cookie(headers: &axum::http::HeaderMap) -> Option<&str> {
    headers.get(axum::http::header::COOKIE)?
        .to_str().ok()?
        .split(';')
        .find_map(|kv| {
            let kv = kv.trim();
            kv.strip_prefix(session::COOKIE_NAME)
                .and_then(|rest| rest.strip_prefix('='))
        })
}

// ── Router ────────────────────────────────────────────────────────────────────

/// Build the UI router. Requires `state` so the session middleware can be wired
/// with the real `AppState` (same pattern as `http::raw_router`).
pub fn ui_router(state: &AppState) -> Router<AppState> {
    // Public routes — no session required
    let public = Router::new()
        .route("/ui/login",        get(handlers::login_page).post(handlers::login_post))
        .route("/ui/logout",       get(handlers::logout))
        .route("/public/{*path}",  get(serve_public));

    // Protected routes — session required
    let protected = Router::new()
        .route("/ui",                    get(|| async { Redirect::permanent("/ui/") }))
        .route("/ui/",                   get(handlers::dashboard))
        .route("/ui/tools",              get(handlers::tools_list))
        .route("/ui/tools/{name}",       get(handlers::tool_detail))
        .route("/ui/workspaces",                      get(handlers::workspaces))
        .route("/ui/workspaces/{name}",              get(handlers::workspace_detail))
        .route("/ui/workspaces/{name}/servers",      get(handlers::workspace_servers))
        .route("/ui/workspaces/{name}/components",   get(handlers::workspace_components))
        .route("/ui/workspaces/{name}/dag",          get(handlers::workspace_dag))
        .route("/ui/workspaces/{name}/clusters",          get(handlers::workspace_clusters))
        .route("/ui/workspaces/{name}/workflows",         get(handlers::workspace_workflows))
        .route("/ui/workspaces/{name}/servers/{srv}",     get(handlers::workspace_server_detail))
        .route("/ui/workspaces/{name}/servers/{srv}/scale-down", post(handlers::server_scale_down))
        .route("/ui/workspaces/{name}/volumes/{vol_id}",  get(handlers::workspace_volume_detail))
        .route("/ui/workspaces/{name}/components/{comp}", get(handlers::workspace_component_detail))
        .route("/ui/workspaces/{name}/components/{comp}/pods", get(handlers::workspace_component_pods))
        .route("/ui/workspaces/{name}/components/{comp}/pods/{ns}/{pod}/describe", get(handlers::workspace_component_describe_pod))
        .route("/ui/workspaces/{name}/modes",            get(handlers::workspace_modes))
        .route("/ui/workspaces/{name}/backlog",          get(handlers::workspace_backlog))
        .route("/ui/workspaces/{name}/backlog/add",      post(handlers::workspace_backlog_add))
        .route("/ui/workspaces/{name}/ontology",         get(handlers::workspace_ontology))
        .route("/ui/workspaces/{name}/actions",          get(handlers::workspace_actions))
        .route("/ui/workspaces/{name}/notifications",    get(handlers::workspace_notifications))
        .route("/ui/workspaces/{name}/config",           get(handlers::workspace_config))
        .route("/ui/jobs",               get(handlers::jobs))
        .route("/ui/jobs/{id}",          get(handlers::job_detail))
        .route("/ui/extensions",         get(handlers::extensions))
        .route("/ui/modes",              get(handlers::modes))
        .route("/ui/modes/run",          post(handlers::mode_run))
        .route("/ui/ontology",           get(handlers::ontology))
        .layer(from_fn_with_state(state.clone(), session_guard));

    public.merge(protected)
}
