use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::routes::gateway::current_actor_id;
use axum::extract::{Extension, Path, Query, State};
use axum::http::StatusCode;
use axum::Json;
use control_plane::{AppendAuditInput, SidebarShortcutRecord, UpsertSidebarShortcutInput};
use serde::Deserialize;
use serde_json::{json, Value};

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SidebarShortcutListQuery {
    #[serde(default, alias = "workspace_id")]
    workspace_id: String,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SidebarShortcutUpsertRequest {
    #[serde(default)]
    id: Option<String>,
    #[serde(default, alias = "workspace_id")]
    workspace_id: String,
    #[serde(default, alias = "root_uri")]
    root_uri: Option<String>,
    #[serde(default)]
    kind: String,
    #[serde(default, alias = "source_kind")]
    source_kind: String,
    #[serde(default, alias = "target_id")]
    target_id: String,
    #[serde(default, alias = "relative_path")]
    relative_path: Option<String>,
    #[serde(default, alias = "document_id")]
    document_id: Option<String>,
    #[serde(default)]
    title: String,
    #[serde(default)]
    icon: Option<String>,
    #[serde(default, alias = "sort_order")]
    sort_order: Option<i64>,
    #[serde(default, alias = "metadata_json")]
    metadata_json: Option<String>,
}

pub async fn list_shortcuts(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Query(query): Query<SidebarShortcutListQuery>,
) -> Result<Json<Value>, WebError> {
    let workspace_id = query.workspace_id.trim();
    if workspace_id.is_empty() {
        return Err(WebError::bad_request_code(
            "sidebar_shortcut_workspace_required",
            "缺少 workspaceId",
        )
        .with_context(&context));
    }
    let actor_id = require_actor_id(&state, &context)?;
    let shortcuts = state
        .control_plane()
        .list_sidebar_shortcuts(&actor_id, workspace_id)
        .map_err(|error| {
            WebError::internal(format!("读取星标置顶失败: {error}")).with_context(&context)
        })?;
    Ok(Json(json!({
        "ok": true,
        "owner": "mnote-web",
        "workspaceId": workspace_id,
        "shortcuts": shortcuts.iter().map(shortcut_to_json).collect::<Vec<_>>(),
    })))
}

pub async fn upsert_shortcut(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Json(request): Json<SidebarShortcutUpsertRequest>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    let workspace_id = request.workspace_id.trim().to_string();
    if workspace_id.is_empty() {
        return Err(WebError::bad_request_code(
            "sidebar_shortcut_workspace_required",
            "缺少 workspaceId",
        )
        .with_context(&context));
    }
    let kind = request.kind.trim().to_string();
    let target_id = request.target_id.trim().to_string();
    let title = request.title.trim().to_string();
    let root_uri = request
        .root_uri
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty());
    let metadata_json =
        normalize_shortcut_metadata_json(request.metadata_json, root_uri.as_deref())?;
    let shortcut = state
        .control_plane()
        .upsert_sidebar_shortcut(UpsertSidebarShortcutInput {
            id: request.id,
            user_id: actor_id.clone(),
            workspace_id: workspace_id.clone(),
            root_uri,
            kind,
            source_kind: request
                .source_kind
                .trim()
                .to_string()
                .if_empty_else(|| "local_folder".to_string()),
            target_id,
            relative_path: request
                .relative_path
                .map(|value| value.trim().trim_matches('/').to_string())
                .filter(|value| !value.is_empty()),
            document_id: request
                .document_id
                .map(|value| value.trim().to_string())
                .filter(|value| !value.is_empty()),
            title,
            icon: request
                .icon
                .map(|value| value.trim().to_string())
                .filter(|value| !value.is_empty()),
            sort_order: request.sort_order.unwrap_or(0),
            metadata_json,
        })
        .map_err(|error| {
            WebError::bad_request_code(
                "sidebar_shortcut_upsert_failed",
                format!("写入星标置顶失败: {error}"),
            )
            .with_context(&context)
        })?;
    append_shortcut_audit(&state, &actor_id, "sidebar.shortcut.upserted", &shortcut);
    Ok(Json(json!({
        "ok": true,
        "owner": "mnote-web",
        "shortcut": shortcut_to_json(&shortcut),
    })))
}

pub async fn delete_shortcut(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Path(shortcut_id): Path<String>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    state
        .control_plane()
        .delete_sidebar_shortcut(&actor_id, &shortcut_id)
        .map_err(|error| {
            WebError::bad_request_code(
                "sidebar_shortcut_delete_failed",
                format!("移除星标置顶失败: {error}"),
            )
            .with_context(&context)
        })?;
    let _ = state.control_plane().append_audit(AppendAuditInput {
        actor_user_id: Some(actor_id),
        action: "sidebar.shortcut.removed".to_string(),
        target_kind: "sidebar_shortcut".to_string(),
        target_id: Some(shortcut_id.clone()),
        metadata_json: "{}".to_string(),
    });
    Ok(Json(json!({
        "ok": true,
        "owner": "mnote-web",
        "removedShortcutId": shortcut_id,
    })))
}

pub(crate) fn load_sidebar_shortcut_dataset(
    state: &AppState,
    context: &RequestContext,
    workspace_id: &str,
) -> Vec<Value> {
    let Some(actor_id) = current_actor_id(state, context) else {
        return Vec::new();
    };
    state
        .control_plane()
        .list_sidebar_shortcuts_with_global_local(&actor_id, workspace_id)
        .map(|shortcuts| shortcuts.iter().map(shortcut_to_json).collect())
        .unwrap_or_default()
}

fn require_actor_id(state: &AppState, context: &RequestContext) -> Result<String, WebError> {
    current_actor_id(state, context)
        .filter(|actor_id| !actor_id.trim().is_empty() && actor_id.trim() != "anonymous")
        .ok_or_else(|| {
            WebError::new(
                StatusCode::UNAUTHORIZED,
                "sidebar_shortcut_auth_required",
                "星标置顶需要登录用户",
            )
            .with_context(context)
        })
}

fn shortcut_to_json(shortcut: &SidebarShortcutRecord) -> Value {
    json!({
        "id": shortcut.id,
        "userId": shortcut.user_id,
        "workspaceId": shortcut.workspace_id,
        "rootUri": shortcut.root_uri,
        "kind": shortcut.kind,
        "sourceKind": shortcut.source_kind,
        "targetId": shortcut.target_id,
        "relativePath": shortcut.relative_path,
        "documentId": shortcut.document_id,
        "title": shortcut.title,
        "icon": shortcut.icon,
        "sortOrder": shortcut.sort_order,
        "status": shortcut.status,
        "metadata": serde_json::from_str::<Value>(&shortcut.metadata_json).unwrap_or(Value::Null),
        "createdAt": shortcut.created_at,
        "updatedAt": shortcut.updated_at,
        "revision": shortcut.revision,
    })
}

fn normalize_shortcut_metadata_json(
    metadata_json: Option<String>,
    root_uri: Option<&str>,
) -> Result<String, WebError> {
    let mut metadata = metadata_json
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| serde_json::from_str::<Value>(value))
        .transpose()
        .map_err(|error| {
            WebError::bad_request_code(
                "sidebar_shortcut_metadata_invalid",
                format!("星标置顶 metadataJson 必须是 JSON 对象: {error}"),
            )
        })?
        .unwrap_or_else(|| json!({}));
    if !metadata.is_object() {
        metadata = json!({});
    }
    if let Some(root_uri) = root_uri.map(str::trim).filter(|value| !value.is_empty()) {
        if let Some(object) = metadata.as_object_mut() {
            object.insert("rootUri".to_string(), Value::String(root_uri.to_string()));
        }
    }
    serde_json::to_string(&metadata)
        .map_err(|error| WebError::internal(format!("星标置顶 metadataJson 序列化失败: {error}")))
}

fn append_shortcut_audit(
    state: &AppState,
    actor_id: &str,
    action: &str,
    shortcut: &SidebarShortcutRecord,
) {
    let _ = state.control_plane().append_audit(AppendAuditInput {
        actor_user_id: Some(actor_id.to_string()),
        action: action.to_string(),
        target_kind: "sidebar_shortcut".to_string(),
        target_id: Some(shortcut.id.clone()),
        metadata_json: json!({
            "workspaceId": shortcut.workspace_id,
            "kind": shortcut.kind,
            "targetId": shortcut.target_id,
        })
        .to_string(),
    });
}

trait EmptyStringExt {
    fn if_empty_else(self, fallback: impl FnOnce() -> String) -> String;
}

impl EmptyStringExt for String {
    fn if_empty_else(self, fallback: impl FnOnce() -> String) -> String {
        if self.trim().is_empty() {
            fallback()
        } else {
            self
        }
    }
}
