use std::sync::Arc;

use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::Html;
use axum::Json;
use serde::Deserialize;
use tera::Context;

use super::super::auth::AuthUser;
use super::common::{auth_role_str, insert_brand_ctx, now_iso, render, tera_ref, UiError};
use crate::api::AppState;

pub(crate) async fn load_bookmark_entries(
    cache: &Arc<crate::cache::NclCache>,
    root: &std::path::Path,
    import_path: Option<&str>,
) -> Vec<serde_json::Value> {
    let bm_path =
        ontoref_ontology::layout::resolve_section(root, "reflection/search_bookmarks.ncl");
    if !bm_path.exists() {
        return vec![];
    }
    match cache.export(&bm_path, import_path).await {
        Ok((json, _)) => json
            .get("entries")
            .and_then(|v| v.as_array())
            .cloned()
            .unwrap_or_default(),
        Err(_) => vec![],
    }
}

fn resolve_bookmark_ctx(
    state: &crate::api::AppState,
    slug: Option<&str>,
) -> (std::path::PathBuf, Arc<crate::cache::NclCache>) {
    if let Some(s) = slug {
        if let Some(ctx) = state.registry.get(s) {
            return (ctx.root.clone(), ctx.cache.clone());
        }
    }
    (state.project_root.clone(), state.cache.clone())
}

pub async fn search_page(State(state): State<AppState>) -> Result<Html<String>, UiError> {
    let tera = tera_ref(&state)?;
    let bookmarks = load_bookmark_entries(
        &state.cache,
        &state.project_root,
        state.nickel_import_path.as_deref(),
    )
    .await;
    let mut ctx = Context::new();
    ctx.insert("base_url", "/ui");
    ctx.insert("slug", &Option::<String>::None);
    ctx.insert("server_bookmarks", &bookmarks);
    insert_brand_ctx(
        &mut ctx,
        &state.project_root,
        &state.cache,
        state.nickel_import_path.as_deref(),
        "/ui",
        &state.registry,
        &state.service_flags,
    )
    .await;
    render(tera, "pages/search.html", &ctx).await
}

pub async fn search_page_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
) -> Result<Html<String>, UiError> {
    let tera = tera_ref(&state)?;
    let ctx_ref = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let base_url = format!("/ui/{slug}");
    let bookmarks = load_bookmark_entries(
        &ctx_ref.cache,
        &ctx_ref.root,
        ctx_ref.import_path.as_deref(),
    )
    .await;
    let mut ctx = Context::new();
    ctx.insert("base_url", &base_url);
    ctx.insert("slug", &slug);
    ctx.insert("current_role", &auth_role_str(&auth));
    ctx.insert("server_bookmarks", &bookmarks);
    insert_brand_ctx(
        &mut ctx,
        &ctx_ref.root,
        &ctx_ref.cache,
        ctx_ref.import_path.as_deref(),
        &base_url,
        &state.registry,
        &state.service_flags,
    )
    .await;
    render(tera, "pages/search.html", &ctx).await
}

#[derive(Deserialize)]
pub struct BookmarkAddRequest {
    pub node_id: String,
    pub kind: Option<String>,
    pub title: String,
    pub level: Option<String>,
    pub term: Option<String>,
    pub actor: Option<String>,
    pub tags: Option<Vec<String>>,
    pub slug: Option<String>,
}

#[derive(Deserialize)]
pub struct BookmarkDeleteRequest {
    pub id: String,
    pub slug: Option<String>,
}

pub async fn search_bookmark_add(
    State(state): State<AppState>,
    Json(body): Json<BookmarkAddRequest>,
) -> impl axum::response::IntoResponse {
    let (root, cache) = resolve_bookmark_ctx(&state, body.slug.as_deref());
    let bm_path =
        ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl");
    let _guard = state.ncl_write_lock.acquire(&bm_path).await;
    if !bm_path.exists() {
        return (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({
                "error": "search_bookmarks.ncl not found — run ontoref setup first"
            })),
        );
    }

    let kind = body.kind.as_deref().unwrap_or("node");
    let level = body.level.as_deref().unwrap_or("");
    let term = body.term.as_deref().unwrap_or("");
    let actor = body.actor.as_deref().unwrap_or("human");
    let tags = body.tags.as_deref().unwrap_or(&[]);
    let now = now_iso();

    match super::super::search_bookmarks_ncl::add_entry(
        &bm_path,
        super::super::search_bookmarks_ncl::NewBookmark {
            node_id: &body.node_id,
            kind,
            title: &body.title,
            level,
            term,
            actor,
            created_at: &now,
            tags,
        },
    ) {
        Ok(id) => {
            cache.invalidate_file(&bm_path);
            (
                StatusCode::OK,
                Json(serde_json::json!({
                    "ok":         true,
                    "id":         id,
                    "created_at": now,
                    "node_id":    body.node_id,
                })),
            )
        }
        Err(e) => {
            tracing::warn!(error = %e, "search_bookmark_add failed");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": e.to_string() })),
            )
        }
    }
}

pub async fn search_bookmark_delete(
    State(state): State<AppState>,
    Json(body): Json<BookmarkDeleteRequest>,
) -> impl axum::response::IntoResponse {
    let (root, cache) = resolve_bookmark_ctx(&state, body.slug.as_deref());
    let bm_path =
        ontoref_ontology::layout::resolve_section(&root, "reflection/search_bookmarks.ncl");
    let _guard = state.ncl_write_lock.acquire(&bm_path).await;
    if !bm_path.exists() {
        return (
            StatusCode::NOT_FOUND,
            Json(serde_json::json!({ "error": "search_bookmarks.ncl not found" })),
        );
    }
    match super::super::search_bookmarks_ncl::remove_entry(&bm_path, &body.id) {
        Ok(()) => {
            cache.invalidate_file(&bm_path);
            (StatusCode::OK, Json(serde_json::json!({ "ok": true })))
        }
        Err(e) => {
            tracing::warn!(error = %e, "search_bookmark_delete failed");
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({ "error": e.to_string() })),
            )
        }
    }
}
