use std::sync::Arc;

use axum::extract::{Form, Path, State};
use axum::response::{Html, IntoResponse, Redirect, Response};
use serde::{Deserialize, Serialize};
use tera::Context;

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

#[derive(Deserialize)]
pub struct BacklogStatusForm {
    pub id: String,
    pub status: String,
}

#[derive(Deserialize)]
pub struct BacklogAddForm {
    pub title: String,
    pub kind: String,
    pub priority: String,
    pub detail: String,
}

#[derive(Deserialize)]
pub struct BacklogEditForm {
    pub id: String,
    pub title: String,
    pub kind: String,
    pub priority: String,
    pub status: String,
    pub detail: String,
    /// Comma-separated ADR ids, e.g. "adr-001-title,adr-002-title"
    #[serde(default)]
    pub related_adrs: String,
    /// Comma-separated mode ids
    #[serde(default)]
    pub related_modes: String,
    /// Enum value: Adr | Mode | StateTransition | PrItem | "" (keep existing)
    #[serde(default)]
    pub graduates_to: String,
}

#[derive(Deserialize)]
pub struct BacklogDeleteForm {
    pub id: String,
}

#[derive(Deserialize)]
pub struct BacklogProposeForm {
    pub id: String,
    pub proposed_status: String,
    /// Optional actor label included in the notification title.
    #[serde(default)]
    pub proposed_by: String,
}

#[derive(Serialize)]
pub(crate) struct BacklogStats {
    pub total: usize,
    pub open: usize,
    pub inprog: usize,
    pub done: usize,
    pub cancelled: usize,
    pub critical: usize,
}

pub(crate) async fn load_backlog_items(
    cache: &Arc<crate::cache::NclCache>,
    backlog_path: &std::path::Path,
    import_path: Option<&str>,
) -> Vec<serde_json::Value> {
    let Ok((json, _)) = cache.export(backlog_path, import_path).await else {
        return vec![];
    };
    json.get("items")
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default()
}

pub(crate) fn backlog_stats(items: &[serde_json::Value]) -> BacklogStats {
    let count = |key: &str, val: &str| {
        items
            .iter()
            .filter(|it| it.get(key).and_then(|v| v.as_str()) == Some(val))
            .count()
    };
    BacklogStats {
        total: items.len(),
        open: count("status", "Open"),
        inprog: count("status", "InProgress"),
        done: count("status", "Done"),
        cancelled: count("status", "Cancelled"),
        critical: count("priority", "Critical"),
    }
}

pub async fn backlog_page(State(state): State<AppState>) -> Result<Html<String>, UiError> {
    let tera = tera_ref(&state)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl");
    let items = if backlog_path.exists() {
        load_backlog_items(
            &state.cache,
            &backlog_path,
            state.nickel_import_path.as_deref(),
        )
        .await
    } else {
        vec![]
    };
    let stats = backlog_stats(&items);
    let mut ctx = Context::new();
    ctx.insert("items", &items);
    ctx.insert("stats", &stats);
    ctx.insert("has_backlog", &backlog_path.exists());
    ctx.insert("base_url", "/ui");
    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/backlog.html", &ctx).await
}

pub async fn backlog_page_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
) -> Result<Html<String>, UiError> {
    let tera = tera_ref(&state)?;
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let items = if backlog_path.exists() {
        load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await
    } else {
        vec![]
    };
    proj.cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone()));
    let stats = backlog_stats(&items);
    let base_url = format!("/ui/{slug}");
    let mut ctx = Context::new();
    ctx.insert("items", &items);
    ctx.insert("stats", &stats);
    ctx.insert("has_backlog", &backlog_path.exists());
    ctx.insert("base_url", &base_url);
    ctx.insert("slug", &slug);
    ctx.insert("current_role", &auth_role_str(&auth));
    insert_brand_ctx(
        &mut ctx,
        &proj.root,
        &proj.cache,
        proj.import_path.as_deref(),
        &base_url,
        &state.registry,
        &state.service_flags,
    )
    .await;
    render(tera, "pages/backlog.html", &ctx).await
}

pub async fn backlog_update_status(
    State(state): State<AppState>,
    Form(form): Form<BacklogStatusForm>,
) -> Result<Response, UiError> {
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::update_status(&backlog_path, &form.id, &form.status, &today_iso())
        .map_err(|e| UiError::NclExport {
        path: "backlog.ncl".into(),
        reason: e.to_string(),
    })?;
    state
        .cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path));
    Ok(Redirect::to("/ui/backlog").into_response())
}

pub async fn backlog_update_status_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
    headers: axum::http::HeaderMap,
    Form(form): Form<BacklogStatusForm>,
) -> Result<Response, UiError> {
    if auth_role_str(&auth) != "admin" {
        return Err(UiError::Forbidden(
            "admin role required for backlog writes".into(),
        ));
    }
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::update_status(&backlog_path, &form.id, &form.status, &today_iso())
        .map_err(|e| UiError::NclExport {
        path: "backlog.ncl".into(),
        reason: e.to_string(),
    })?;
    proj.cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone()));

    if headers.contains_key("hx-request") {
        let tera = tera_ref(&state)?;
        let items =
            load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await;
        let mut ctx = Context::new();
        ctx.insert("items", &items);
        ctx.insert("base_url", &format!("/ui/{slug}"));
        return render(tera, "partials/backlog_items_container.html", &ctx)
            .await
            .map(IntoResponse::into_response);
    }
    Ok(Redirect::to(&format!("/ui/{slug}/backlog")).into_response())
}

pub async fn backlog_add(
    State(state): State<AppState>,
    Form(form): Form<BacklogAddForm>,
) -> Result<Response, UiError> {
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&state.project_root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::add_item(
        &backlog_path,
        &form.title,
        &form.kind,
        &form.priority,
        &form.detail,
        &today_iso(),
    )
    .map_err(|e| UiError::NclExport {
        path: "backlog.ncl".into(),
        reason: e.to_string(),
    })?;
    state
        .cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path));
    Ok(Redirect::to("/ui/backlog").into_response())
}

pub async fn backlog_add_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
    headers: axum::http::HeaderMap,
    Form(form): Form<BacklogAddForm>,
) -> Result<Response, UiError> {
    if auth_role_str(&auth) != "admin" {
        return Err(UiError::Forbidden(
            "admin role required for backlog writes".into(),
        ));
    }
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::add_item(
        &backlog_path,
        &form.title,
        &form.kind,
        &form.priority,
        &form.detail,
        &today_iso(),
    )
    .map_err(|e| UiError::NclExport {
        path: "backlog.ncl".into(),
        reason: e.to_string(),
    })?;
    proj.cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone()));

    if headers.contains_key("hx-request") {
        let tera = tera_ref(&state)?;
        let items =
            load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await;
        let mut ctx = Context::new();
        ctx.insert("items", &items);
        ctx.insert("base_url", &format!("/ui/{slug}"));
        return render(tera, "partials/backlog_items_container.html", &ctx)
            .await
            .map(IntoResponse::into_response);
    }
    Ok(Redirect::to(&format!("/ui/{slug}/backlog")).into_response())
}

pub async fn backlog_edit_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
    headers: axum::http::HeaderMap,
    Form(form): Form<BacklogEditForm>,
) -> Result<Response, UiError> {
    if auth_role_str(&auth) != "admin" {
        return Err(UiError::Forbidden(
            "admin role required for backlog writes".into(),
        ));
    }
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::update_item(
        &backlog_path,
        &form.id,
        &super::super::backlog_ncl::ItemUpdate {
            title: &form.title,
            kind: &form.kind,
            priority: &form.priority,
            status: &form.status,
            detail: &form.detail,
            related_adrs: &form.related_adrs,
            related_modes: &form.related_modes,
            graduates_to: &form.graduates_to,
            today: &today_iso(),
        },
    )
    .map_err(|e| UiError::NclExport {
        path: "backlog.ncl".into(),
        reason: e.to_string(),
    })?;
    proj.cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone()));

    if headers.contains_key("hx-request") {
        let tera = tera_ref(&state)?;
        let items =
            load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await;
        let mut ctx = Context::new();
        ctx.insert("items", &items);
        ctx.insert("base_url", &format!("/ui/{slug}"));
        return render(tera, "partials/backlog_items_container.html", &ctx)
            .await
            .map(IntoResponse::into_response);
    }
    Ok(Redirect::to(&format!("/ui/{slug}/backlog")).into_response())
}

pub async fn backlog_delete_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    auth: AuthUser,
    headers: axum::http::HeaderMap,
    Form(form): Form<BacklogDeleteForm>,
) -> Result<Response, UiError> {
    if auth_role_str(&auth) != "admin" {
        return Err(UiError::Forbidden(
            "admin role required for backlog writes".into(),
        ));
    }
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;
    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let _guard = state.ncl_write_lock.acquire(&backlog_path).await;
    super::super::backlog_ncl::delete_item(&backlog_path, &form.id).map_err(|e| {
        UiError::NclExport {
            path: "backlog.ncl".into(),
            reason: e.to_string(),
        }
    })?;
    proj.cache
        .invalidate_file(&backlog_path.canonicalize().unwrap_or(backlog_path.clone()));

    if headers.contains_key("hx-request") {
        let tera = tera_ref(&state)?;
        let items =
            load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await;
        let mut ctx = Context::new();
        ctx.insert("items", &items);
        ctx.insert("base_url", &format!("/ui/{slug}"));
        return render(tera, "partials/backlog_items_container.html", &ctx)
            .await
            .map(IntoResponse::into_response);
    }
    Ok(Redirect::to(&format!("/ui/{slug}/backlog")).into_response())
}

pub async fn backlog_propose_status_mp(
    State(state): State<AppState>,
    Path(slug): Path<String>,
    _auth: AuthUser,
    headers: axum::http::HeaderMap,
    Form(form): Form<BacklogProposeForm>,
) -> Result<Response, UiError> {
    let proj = state.registry.get(&slug).ok_or(UiError::NotConfigured)?;

    let backlog_path =
        ontoref_ontology::layout::resolve_section(&proj.root, "reflection/backlog.ncl");
    let items = load_backlog_items(&proj.cache, &backlog_path, proj.import_path.as_deref()).await;
    let item_title = items
        .iter()
        .find(|it| it.get("id").and_then(|v| v.as_str()) == Some(form.id.as_str()))
        .and_then(|it| it.get("title").and_then(|v| v.as_str()))
        .unwrap_or(&form.id)
        .to_string();
    let current_status = items
        .iter()
        .find(|it| it.get("id").and_then(|v| v.as_str()) == Some(form.id.as_str()))
        .and_then(|it| it.get("status").and_then(|v| v.as_str()))
        .unwrap_or("Unknown")
        .to_string();

    let by = if form.proposed_by.is_empty() {
        "actor".to_string()
    } else {
        form.proposed_by.clone()
    };

    let payload = serde_json::json!({
        "item_id":         form.id,
        "item_title":      item_title,
        "current_status":  current_status,
        "proposed_status": form.proposed_status,
        "proposed_by":     by,
        "actions": [
            { "id": "approve", "label": format!("Approve → {}", form.proposed_status), "mode": "backlog_approve" },
            { "id": "reject",  "label": "Reject",  "mode": "backlog_reject" }
        ]
    });

    proj.notifications.push_custom(
        &slug,
        "backlog_review",
        format!("{}: {} → {}", form.id, item_title, form.proposed_status),
        Some(payload),
        Some(form.proposed_by),
        None,
    );

    if headers.contains_key("hx-request") {
        return Ok(axum::response::Html(
            r#"<span class="badge badge-xs badge-warning gap-1">⏳ proposed</span>"#.to_string(),
        )
        .into_response());
    }
    Ok(Redirect::to(&format!("/ui/{slug}/notifications")).into_response())
}
