use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::page_aggregate::{PageAggregate, PageOptions};
use crate::routes::gateway::current_actor_id;
use crate::routes::local_folder_source::{
    local_root_has_workspace_manifest, local_workspace_id_from_root_uri,
};
use axum::extract::{Extension, Query, State};
use axum::http::StatusCode;
use axum::Json;
use control_plane::{UpsertUserInput, UpsertUserUiPreferenceInput, UserUiPreferenceRecord};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::{json, Map};
use std::collections::BTreeMap;

pub(crate) const SOURCE_FAMILY_MY_SPACE: &str = "my_space";
pub(crate) const SOURCE_FAMILY_EXTERNAL_LOCAL_FOLDER: &str = "external_local_folder";

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

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct UiPreferencesUpdateRequest {
    #[serde(default, alias = "workspace_id")]
    workspace_id: String,
    #[serde(default, alias = "source_kind")]
    source_kind: String,
    #[serde(default, alias = "root_uri")]
    root_uri: String,
    #[serde(default, alias = "document_id")]
    document_id: String,
    #[serde(default)]
    updates: BTreeMap<String, Value>,
}

#[derive(Debug, Clone)]
struct PagePreferenceScope {
    workspace_id: String,
    source_kind: String,
    source_family: String,
    document_id: String,
}

#[derive(Debug, Clone)]
struct EffectivePagePreferences {
    scope: PagePreferenceScope,
    page_options: PageOptions,
    page_width_preferences: BTreeMap<String, EffectivePageWidthPreference>,
    ai_preferences: BTreeMap<String, Value>,
    local_ocr_preferences: BTreeMap<String, Value>,
    sources: BTreeMap<String, String>,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct EffectivePageWidthPreference {
    mode: String,
    custom: Option<String>,
    resolved_mode: String,
    resolved_custom: Option<String>,
    css_max_width: String,
    source: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct PageWidthPreferenceValue {
    mode: String,
    custom: Option<String>,
}

const PAGE_WIDTH_CONTENT_TYPES: [&str; 7] = [
    "default", "markdown", "word", "pdf", "excel", "ppt", "mindmap",
];

pub(crate) async fn effective_preferences(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Query(query): Query<UiPreferencesQuery>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    let effective = resolve_effective_page_preferences(
        &state,
        &actor_id,
        &query.workspace_id,
        &query.source_kind,
        &query.root_uri,
        &query.document_id,
        PageOptions::default(),
    )?;
    Ok(Json(effective_preferences_payload(effective)))
}

pub(crate) async fn update_preferences(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Json(request): Json<UiPreferencesUpdateRequest>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    let result = update_page_preferences_from_value(
        &state,
        &context,
        &actor_id,
        &request.workspace_id,
        &request.source_kind,
        &request.root_uri,
        &request.document_id,
        &Value::Object(request.updates.into_iter().collect()),
    )?;
    Ok(Json(json!({
        "ok": true,
        "owner": "mnote-web",
        "result": result,
    })))
}

pub(crate) fn update_page_preferences_from_value(
    state: &AppState,
    context: &RequestContext,
    actor_id: &str,
    workspace_id: &str,
    source_kind: &str,
    root_uri: &str,
    document_id: &str,
    updates: &Value,
) -> Result<Value, WebError> {
    ensure_actor_user(state, actor_id)?;
    let scope = page_preference_scope(workspace_id, source_kind, root_uri, document_id)?;
    let update_map = updates.as_object().ok_or_else(|| {
        WebError::bad_request_code("ui_preference_updates_invalid", "updates 必须是对象")
            .with_context(context)
    })?;
    for (key, value) in update_map {
        let Some((scope_kind, scope_id)) = preference_scope_for_key(key, &scope) else {
            continue;
        };
        state
            .control_plane()
            .upsert_user_ui_preference(UpsertUserUiPreferenceInput {
                id: None,
                user_id: actor_id.to_string(),
                workspace_id: preference_workspace_for_scope(&scope.workspace_id, &scope_kind),
                source_kind: preference_source_kind_for_scope(&scope.source_kind, &scope_kind),
                scope_kind,
                scope_id,
                key: key.trim().to_string(),
                value_json: value.to_string(),
            })
            .map_err(|error| {
                WebError::bad_request_code(
                    "ui_preference_write_failed",
                    format!("写入 UI 偏好失败: {error}"),
                )
                .with_context(context)
            })?;
    }
    let effective = resolve_effective_page_preferences(
        state,
        actor_id,
        workspace_id,
        source_kind,
        root_uri,
        document_id,
        PageOptions::default(),
    )?;
    Ok(effective_preferences_payload(effective)["result"].clone())
}

pub(crate) fn source_family_for_page(
    source_kind: Option<&str>,
    root_uri: Option<&str>,
) -> Result<String, WebError> {
    if source_kind.map(str::trim) != Some("local_folder") {
        return Ok("workspace".to_string());
    }
    let Some(root_uri) = root_uri.map(str::trim).filter(|value| !value.is_empty()) else {
        return Ok(SOURCE_FAMILY_EXTERNAL_LOCAL_FOLDER.to_string());
    };
    if local_root_has_workspace_manifest(root_uri)? {
        Ok(SOURCE_FAMILY_MY_SPACE.to_string())
    } else {
        Ok(SOURCE_FAMILY_EXTERNAL_LOCAL_FOLDER.to_string())
    }
}

pub(crate) fn apply_effective_page_preferences(
    state: &AppState,
    context: &RequestContext,
    aggregate: &mut PageAggregate,
    source_kind: Option<&str>,
    root_uri: Option<&str>,
) -> Result<(), WebError> {
    let actor_id = context.auth.actor_id.trim();
    if actor_id.is_empty() || actor_id == "anonymous" {
        return Ok(());
    }
    let effective = resolve_effective_page_preferences(
        state,
        actor_id,
        &aggregate.identity.workspace_id,
        source_kind.unwrap_or_default(),
        root_uri.unwrap_or_default(),
        &aggregate.identity.document_id,
        aggregate.layout.page_options.clone(),
    )?;
    aggregate.layout.page_options = effective.page_options;
    aggregate.layout_options = serde_json::to_value(&aggregate.layout.page_options)
        .unwrap_or_else(|_| serde_json::json!({}));
    Ok(())
}

fn resolve_effective_page_preferences(
    state: &AppState,
    actor_id: &str,
    workspace_id: &str,
    source_kind: &str,
    root_uri: &str,
    document_id: &str,
    base_options: PageOptions,
) -> Result<EffectivePagePreferences, WebError> {
    let scope = page_preference_scope(workspace_id, source_kind, root_uri, document_id)?;
    let preferences = state
        .control_plane()
        .list_user_ui_preferences(
            actor_id,
            Some(&scope.workspace_id),
            Some(&scope.source_kind),
        )
        .map_err(|error| WebError::internal(format!("SQLite UI 偏好读取失败: {error}")))?;
    let mut page_options = base_options;
    if scope.source_family == SOURCE_FAMILY_EXTERNAL_LOCAL_FOLDER {
        page_options.hide_title_header = true;
    }
    let mut sources = BTreeMap::new();
    let mut page_width_preferences = default_page_width_preferences();
    let mut ai_preferences = BTreeMap::new();
    let mut local_ocr_preferences = default_local_ocr_preferences();
    apply_preference_records(
        &mut page_options,
        &mut page_width_preferences,
        &mut ai_preferences,
        &mut local_ocr_preferences,
        &mut sources,
        &scope,
        &preferences,
    )?;
    resolve_page_width_preferences(&mut page_width_preferences);
    Ok(EffectivePagePreferences {
        scope,
        page_options,
        page_width_preferences,
        ai_preferences,
        local_ocr_preferences,
        sources,
    })
}

fn apply_preference_records(
    page_options: &mut PageOptions,
    page_width_preferences: &mut BTreeMap<String, EffectivePageWidthPreference>,
    ai_preferences: &mut BTreeMap<String, Value>,
    local_ocr_preferences: &mut BTreeMap<String, Value>,
    sources: &mut BTreeMap<String, String>,
    scope: &PagePreferenceScope,
    preferences: &[UserUiPreferenceRecord],
) -> Result<(), WebError> {
    let mut scope_kinds = vec![
        "global".to_string(),
        "source_family".to_string(),
        "workspace".to_string(),
        "document".to_string(),
    ];
    for preference in preferences {
        if preference.scope_kind.starts_with("ai.") && !scope_kinds.contains(&preference.scope_kind)
        {
            scope_kinds.push(preference.scope_kind.clone());
        }
    }
    for scope_kind in scope_kinds {
        for preference in preferences
            .iter()
            .filter(|preference| preference.scope_kind.trim() == scope_kind.as_str())
        {
            let scope_matches = match scope_kind.as_str() {
                "global" => preference.scope_id.trim() == "default",
                "source_family" => preference.scope_id.trim() == scope.source_family,
                "workspace" => preference.scope_id.trim() == scope.workspace_id,
                "document" => preference.scope_id.trim() == scope.document_id,
                kind if kind.starts_with("ai.") => preference.scope_id.trim() == scope.workspace_id,
                _ => false,
            };
            if !scope_matches {
                continue;
            }
            let value = serde_json::from_str::<Value>(&preference.value_json).map_err(|error| {
                WebError::internal(format!(
                    "SQLite UI 偏好 JSON 无效 {}: {error}",
                    preference.key
                ))
            })?;
            if preference.key.starts_with("ai.common.") || preference.key.starts_with("ai.agent.") {
                ai_preferences.insert(preference.key.clone(), value);
                sources.insert(preference.key.clone(), scope_kind.to_string());
                continue;
            }
            if preference.key.starts_with("localOcr.") {
                local_ocr_preferences.insert(preference.key.clone(), Value::Bool(false));
                sources.insert(preference.key.clone(), "retired".to_string());
                continue;
            }
            if let Some(content_type) = page_width_content_type_for_key(&preference.key) {
                if let Some(normalized) = normalize_page_width_preference(content_type, &value) {
                    if let Some(preference_value) = page_width_preferences.get_mut(content_type) {
                        preference_value.mode = normalized.mode;
                        preference_value.custom = normalized.custom;
                        preference_value.source = scope_kind.to_string();
                        sources.insert(preference.key.clone(), scope_kind.to_string());
                    }
                }
                continue;
            }
            if apply_page_option_value(page_options, &preference.key, &value) {
                sources.insert(preference.key.clone(), scope_kind.to_string());
            }
        }
    }
    Ok(())
}

fn page_preference_scope(
    workspace_id: &str,
    source_kind: &str,
    root_uri: &str,
    document_id: &str,
) -> Result<PagePreferenceScope, WebError> {
    let source_kind = source_kind.trim();
    let source_kind = if source_kind.is_empty() {
        "convex_workspace"
    } else {
        source_kind
    };
    let workspace_id = workspace_id.trim().to_string().if_empty_else(|| {
        if source_kind == "local_folder" {
            local_workspace_id_from_root_uri(root_uri).unwrap_or_else(|_| "local-folder".into())
        } else {
            "default".into()
        }
    });
    Ok(PagePreferenceScope {
        workspace_id,
        source_kind: source_kind.to_string(),
        source_family: source_family_for_page(Some(source_kind), Some(root_uri))?,
        document_id: document_id.trim().to_string(),
    })
}

fn preference_scope_for_key(key: &str, scope: &PagePreferenceScope) -> Option<(String, String)> {
    let trimmed = key.trim();
    if trimmed.starts_with("ai.common.") {
        return Some(("ai.common".to_string(), scope.workspace_id.clone()));
    }
    if let Some(rest) = trimmed.strip_prefix("ai.agent.") {
        let agent = rest.split('.').next().unwrap_or_default().trim();
        if !agent.is_empty() {
            return Some((format!("ai.agent.{agent}"), scope.workspace_id.clone()));
        }
    }
    if trimmed.starts_with("localOcr.") {
        return None;
    }
    if page_width_content_type_for_key(key).is_some() {
        return Some(("global".to_string(), "default".to_string()));
    }
    match key.trim() {
        "hideTitleHeader" | "hide_title_header" => {
            Some(("source_family".to_string(), scope.source_family.clone()))
        }
        "showHeadingNumbers" | "show_heading_numbers" | "showWordCount" | "show_word_count" => {
            Some(("global".to_string(), "default".to_string()))
        }
        "wideLayout"
        | "wide_layout"
        | "smallText"
        | "small_text"
        | "layoutDensity"
        | "layout_density"
        | "pageFont"
        | "page_font"
        | "showToc"
        | "show_toc"
        | "showStructure"
        | "show_structure"
        | "collapseBacklinks"
        | "collapse_backlinks"
        | "hideChildPages"
        | "hide_child_pages"
        | "showBlockRefCount"
        | "show_block_ref_count" => Some(("workspace".to_string(), scope.workspace_id.clone())),
        _ => None,
    }
}

fn preference_workspace_for_scope(workspace_id: &str, scope_kind: &str) -> Option<String> {
    if scope_kind == "workspace" || scope_kind == "document" || scope_kind.starts_with("ai.") {
        Some(workspace_id.to_string())
    } else {
        None
    }
}

fn preference_source_kind_for_scope(source_kind: &str, scope_kind: &str) -> Option<String> {
    if scope_kind == "workspace" || scope_kind == "document" || scope_kind.starts_with("ai.") {
        Some(source_kind.to_string())
    } else {
        None
    }
}

fn effective_preferences_payload(effective: EffectivePagePreferences) -> Value {
    let page_options = serde_json::to_value(&effective.page_options).unwrap_or_else(|_| json!({}));
    let page_width_preferences =
        serde_json::to_value(&effective.page_width_preferences).unwrap_or_else(|_| json!({}));
    let ai_preferences =
        serde_json::to_value(&effective.ai_preferences).unwrap_or_else(|_| json!({}));
    let local_ocr_preferences =
        serde_json::to_value(&effective.local_ocr_preferences).unwrap_or_else(|_| json!({}));
    let sources = effective
        .sources
        .into_iter()
        .map(|(key, value)| (key, Value::String(value)))
        .collect::<Map<String, Value>>();
    json!({
        "ok": true,
        "owner": "mnote-web",
        "result": {
            "scope": {
                "sourceFamily": effective.scope.source_family,
                "workspaceId": effective.scope.workspace_id,
                "sourceKind": effective.scope.source_kind,
                "documentId": effective.scope.document_id,
            },
            "pageOptions": page_options,
            "pageWidthPreferences": page_width_preferences,
            "aiPreferences": ai_preferences,
            "localOcrPreferences": local_ocr_preferences,
            "sources": Value::Object(sources),
        }
    })
}

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,
                "ui_preference_auth_required",
                "UI 偏好需要登录用户",
            )
            .with_context(context)
        })
}

fn ensure_actor_user(state: &AppState, actor_id: &str) -> Result<(), WebError> {
    state
        .control_plane()
        .upsert_user(UpsertUserInput {
            id: Some(actor_id.to_string()),
            email: None,
            username: actor_id.to_string(),
            display_name: actor_id.to_string(),
            role: None,
            password_hash: None,
        })
        .map(|_| ())
        .map_err(|error| WebError::internal(format!("SQLite 用户初始化失败: {error}")))
}

fn apply_page_option_value(options: &mut PageOptions, key: &str, value: &Value) -> bool {
    match key {
        "hideTitleHeader" | "hide_title_header" => {
            if let Some(value) = value.as_bool() {
                options.hide_title_header = value;
                return true;
            }
        }
        "showHeadingNumbers" | "show_heading_numbers" => {
            if let Some(value) = value.as_bool() {
                options.show_heading_numbers = value;
                return true;
            }
        }
        "wideLayout" | "wide_layout" => {
            if let Some(value) = value.as_bool() {
                options.wide_layout = value;
                return true;
            }
        }
        "smallText" | "small_text" => {
            if let Some(value) = value.as_bool() {
                options.small_text = value;
                return true;
            }
        }
        "layoutDensity" | "layout_density" => {
            if let Some(value) = value
                .as_str()
                .map(str::trim)
                .filter(|value| !value.is_empty())
            {
                options.layout_density = value.to_string();
                return true;
            }
        }
        "pageFont" | "page_font" => {
            if let Some(value) = value
                .as_str()
                .map(str::trim)
                .filter(|value| !value.is_empty())
            {
                options.page_font = value.to_string();
                return true;
            }
        }
        "showToc" | "show_toc" => {
            if let Some(value) = value.as_bool() {
                options.show_toc = value;
                return true;
            }
        }
        "showStructure" | "show_structure" => {
            if let Some(value) = value.as_bool() {
                options.show_structure = value;
                return true;
            }
        }
        "showWordCount" | "show_word_count" => {
            if let Some(value) = value.as_bool() {
                options.show_word_count = value;
                return true;
            }
        }
        "collapseBacklinks" | "collapse_backlinks" => {
            if let Some(value) = value.as_bool() {
                options.collapse_backlinks = value;
                return true;
            }
        }
        "hideChildPages" | "hide_child_pages" => {
            if let Some(value) = value.as_bool() {
                options.hide_child_pages = value;
                return true;
            }
        }
        "showBlockRefCount" | "show_block_ref_count" => {
            if let Some(value) = value.as_bool() {
                options.show_block_ref_count = value;
                return true;
            }
        }
        _ => {}
    }
    false
}

fn page_width_content_type_for_key(key: &str) -> Option<&'static str> {
    let content_type = key.trim().strip_prefix("pageWidth.")?;
    PAGE_WIDTH_CONTENT_TYPES
        .iter()
        .copied()
        .find(|candidate| *candidate == content_type)
}

fn default_page_width_preferences() -> BTreeMap<String, EffectivePageWidthPreference> {
    PAGE_WIDTH_CONTENT_TYPES
        .into_iter()
        .map(|content_type| {
            let mode = match content_type {
                "default" => "comfortable",
                "markdown" => "readable",
                "word" | "pdf" | "ppt" => "wide",
                "excel" | "mindmap" => "full",
                _ => "comfortable",
            };
            (
                content_type.to_string(),
                EffectivePageWidthPreference {
                    mode: mode.to_string(),
                    custom: None,
                    resolved_mode: mode.to_string(),
                    resolved_custom: None,
                    css_max_width: page_width_css_max_width(mode).to_string(),
                    source: "system".to_string(),
                },
            )
        })
        .collect()
}

fn default_local_ocr_preferences() -> BTreeMap<String, Value> {
    BTreeMap::from([("localOcr.autoEnabled".to_string(), Value::Bool(false))])
}

fn normalize_page_width_preference(
    content_type: &str,
    value: &Value,
) -> Option<PageWidthPreferenceValue> {
    let mode = value
        .as_object()
        .and_then(|object| object.get("mode"))
        .and_then(Value::as_str)
        .or_else(|| value.as_str())
        .map(str::trim)
        .filter(|mode| !mode.is_empty())?;
    let mode = if page_width_mode_is_supported(mode) {
        mode
    } else {
        return None;
    };
    let mode = if content_type == "default" && mode == "inherit" {
        "comfortable"
    } else {
        mode
    };
    let custom = value
        .as_object()
        .and_then(|object| object.get("custom"))
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|custom| !custom.is_empty())
        .map(ToOwned::to_owned);
    Some(PageWidthPreferenceValue {
        mode: mode.to_string(),
        custom,
    })
}

fn page_width_mode_is_supported(mode: &str) -> bool {
    matches!(
        mode,
        "inherit" | "readable" | "comfortable" | "wide" | "full" | "custom"
    )
}

fn resolve_page_width_preferences(
    preferences: &mut BTreeMap<String, EffectivePageWidthPreference>,
) {
    let default_mode = preferences
        .get("default")
        .map(|preference| preference.mode.as_str())
        .filter(|mode| *mode != "inherit")
        .unwrap_or("comfortable")
        .to_string();
    let default_custom = preferences
        .get("default")
        .and_then(|preference| preference.custom.clone());

    for content_type in PAGE_WIDTH_CONTENT_TYPES {
        let Some(preference) = preferences.get_mut(content_type) else {
            continue;
        };
        let resolved_mode = if content_type != "default" && preference.mode == "inherit" {
            default_mode.clone()
        } else if preference.mode == "inherit" {
            "comfortable".to_string()
        } else {
            preference.mode.clone()
        };
        let resolved_custom = if content_type != "default" && preference.mode == "inherit" {
            default_custom.clone()
        } else {
            preference.custom.clone()
        };
        preference.resolved_mode = resolved_mode.clone();
        preference.resolved_custom = resolved_custom;
        preference.css_max_width = page_width_css_max_width(&resolved_mode).to_string();
    }
}

pub(crate) fn page_width_css_max_width(mode: &str) -> &'static str {
    match mode {
        "readable" => "760px",
        "comfortable" => "980px",
        "wide" => "1180px",
        "full" => "none",
        "custom" => "980px",
        _ => "980px",
    }
}

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
        }
    }
}
