use crate::app::AppState;
use crate::context::RequestContext;
use crate::error::WebError;
use crate::routes::gateway::current_actor_id;
use crate::routes::local_folder_source::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;
use serde_json::{json, Value};

const SIDEBAR_TREE_SCOPE_KIND: &str = "sidebar_tree";
const SIDEBAR_TREE_VIEW_STATE_KEY: &str = "sidebarTreeViewState.v1";
const MAX_STATE_ITEMS: usize = 512;
const MAX_ID_LENGTH: usize = 2048;

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

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

#[derive(Debug, Clone, PartialEq, Eq)]
struct TreeViewStateScope {
    workspace_id: String,
    source_kind: String,
    tree_kind: String,
    root_uri: String,
    scope: String,
    scope_id: String,
}

pub(crate) async fn get_tree_view_state(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Query(query): Query<TreeViewStateQuery>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    let scope = resolve_scope(
        &context,
        &query.workspace_id,
        &query.source_kind,
        &query.tree_kind,
        &query.root_uri,
        &query.scope,
    )?;
    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 tree view state 读取失败: {error}"))
                .with_context(&context)
        })?;
    let Some(record) = find_tree_view_state_record(&preferences, &scope) else {
        return Ok(Json(response_payload(
            &actor_id,
            &scope,
            "default",
            None,
            default_state(&scope),
        )));
    };
    let parsed = serde_json::from_str::<Value>(&record.value_json).map_err(|error| {
        WebError::internal(format!("SQLite tree view state JSON 无效: {error}"))
            .with_context(&context)
    })?;
    let normalized = normalize_state(&context, &scope, parsed)?;
    Ok(Json(response_payload(
        &actor_id,
        &scope,
        "sqlite",
        Some(record.revision),
        normalized,
    )))
}

pub(crate) async fn put_tree_view_state(
    State(state): State<AppState>,
    Extension(context): Extension<RequestContext>,
    Json(request): Json<TreeViewStateUpdateRequest>,
) -> Result<Json<Value>, WebError> {
    let actor_id = require_actor_id(&state, &context)?;
    ensure_actor_user(&state, &actor_id)?;
    let scope = resolve_scope(
        &context,
        &request.workspace_id,
        &request.source_kind,
        &request.tree_kind,
        &request.root_uri,
        &request.scope,
    )?;
    let normalized = normalize_state(&context, &scope, request.state)?;
    let record = state
        .control_plane()
        .upsert_user_ui_preference(UpsertUserUiPreferenceInput {
            id: None,
            user_id: actor_id.clone(),
            workspace_id: Some(scope.workspace_id.clone()),
            source_kind: Some(scope.source_kind.clone()),
            scope_kind: SIDEBAR_TREE_SCOPE_KIND.to_string(),
            scope_id: scope.scope_id.clone(),
            key: SIDEBAR_TREE_VIEW_STATE_KEY.to_string(),
            value_json: normalized.to_string(),
        })
        .map_err(|error| {
            WebError::bad_request_code(
                "tree_view_state_write_failed",
                format!("写入 tree view state 失败: {error}"),
            )
            .with_context(&context)
        })?;
    Ok(Json(response_payload(
        &actor_id,
        &scope,
        "sqlite",
        Some(record.revision),
        normalized,
    )))
}

fn find_tree_view_state_record<'a>(
    preferences: &'a [UserUiPreferenceRecord],
    scope: &TreeViewStateScope,
) -> Option<&'a UserUiPreferenceRecord> {
    preferences.iter().find(|preference| {
        preference.scope_kind == SIDEBAR_TREE_SCOPE_KIND
            && preference.scope_id == scope.scope_id
            && preference.key == SIDEBAR_TREE_VIEW_STATE_KEY
    })
}

fn resolve_scope(
    context: &RequestContext,
    workspace_id: &str,
    source_kind: &str,
    tree_kind: &str,
    root_uri: &str,
    scope: &str,
) -> Result<TreeViewStateScope, WebError> {
    let source_kind = normalize_or_default(source_kind, "convex_workspace");
    let tree_kind = normalize_tree_kind(context, tree_kind)?;
    let root_uri = root_uri.trim().to_string();
    let scope = normalize_or_default(scope, "root");
    let workspace_id = if source_kind == "local_folder" {
        local_workspace_id_from_root_uri(&root_uri).map_err(|error| error.with_context(context))?
    } else {
        normalize_or_default(workspace_id, "default")
    };
    let scope_id = format!(
        "{}:{}:{}",
        tree_kind,
        stable_hash_16(&root_uri),
        stable_hash_16(&scope)
    );
    Ok(TreeViewStateScope {
        workspace_id,
        source_kind,
        tree_kind,
        root_uri,
        scope,
        scope_id,
    })
}

fn normalize_tree_kind(context: &RequestContext, tree_kind: &str) -> Result<String, WebError> {
    match tree_kind.trim() {
        "filetree" => Ok("filetree".to_string()),
        "pagetree" => Ok("pagetree".to_string()),
        _ => Err(WebError::bad_request_code(
            "tree_view_state_tree_kind_invalid",
            "treeKind 只允许 filetree 或 pagetree",
        )
        .with_context(context)),
    }
}

fn normalize_state(
    context: &RequestContext,
    scope: &TreeViewStateScope,
    value: Value,
) -> Result<Value, WebError> {
    let mut object = value.as_object().cloned().ok_or_else(|| {
        WebError::bad_request_code("tree_view_state_invalid", "state 必须是对象")
            .with_context(context)
    })?;
    let schema_version = object
        .get("schemaVersion")
        .and_then(Value::as_i64)
        .unwrap_or(1);
    if schema_version != 1 {
        return Err(WebError::bad_request_code(
            "tree_view_state_schema_invalid",
            "schemaVersion 必须为 1",
        )
        .with_context(context));
    }
    for derived_key in [
        "rowsByParent",
        "loadedParents",
        "loadingParents",
        "dirtyParents",
        "staleParents",
        "revisionByParent",
        "latestGenerationByParent",
    ] {
        if object.contains_key(derived_key) {
            return Err(WebError::bad_request_code(
                "tree_view_state_contains_derived_cache",
                format!("tree view state 不能包含派生运行态 {derived_key}"),
            )
            .with_context(context));
        }
    }
    object.insert("schemaVersion".to_string(), json!(1));
    object.insert("treeKind".to_string(), json!(scope.tree_kind));
    object.insert("rootUri".to_string(), json!(scope.root_uri));
    object.insert("scope".to_string(), json!(scope.scope));
    object.insert(
        "expandedIds".to_string(),
        json!(bounded_string_array(
            object.get("expandedIds"),
            MAX_STATE_ITEMS
        )),
    );
    object.insert(
        "expandedRelativePaths".to_string(),
        json!(bounded_string_array(
            object.get("expandedRelativePaths"),
            MAX_STATE_ITEMS
        )),
    );
    for key in ["selectedId", "focusedId", "activeId"] {
        object.insert(key.to_string(), json!(bounded_string(object.get(key))));
    }
    object.insert(
        "scrollTop".to_string(),
        json!(object
            .get("scrollTop")
            .and_then(Value::as_f64)
            .filter(|value| value.is_finite() && *value >= 0.0)
            .unwrap_or(0.0)),
    );
    if !object.contains_key("updatedAtMs") {
        object.insert("updatedAtMs".to_string(), json!(0));
    }
    Ok(Value::Object(object))
}

fn default_state(scope: &TreeViewStateScope) -> Value {
    normalize_state(
        &RequestContext::from_http_parts(
            &axum::http::Method::GET,
            &"/api/tree/view-state".parse().expect("static uri"),
            &axum::http::HeaderMap::new(),
        ),
        scope,
        json!({ "schemaVersion": 1 }),
    )
    .expect("default tree view state is valid")
}

fn response_payload(
    actor_id: &str,
    scope: &TreeViewStateScope,
    source: &str,
    revision: Option<i64>,
    state: Value,
) -> Value {
    json!({
        "ok": true,
        "owner": "mnote-web",
        "result": {
            "userId": actor_id,
            "workspaceId": scope.workspace_id,
            "sourceKind": scope.source_kind,
            "treeKind": scope.tree_kind,
            "rootUri": scope.root_uri,
            "scope": scope.scope,
            "scopeId": scope.scope_id,
            "scopeKind": SIDEBAR_TREE_SCOPE_KIND,
            "key": SIDEBAR_TREE_VIEW_STATE_KEY,
            "source": source,
            "revision": revision,
            "state": state
        }
    })
}

fn bounded_string_array(value: Option<&Value>, limit: usize) -> Vec<String> {
    value
        .and_then(Value::as_array)
        .map(|items| {
            items
                .iter()
                .filter_map(Value::as_str)
                .map(str::trim)
                .filter(|item| !item.is_empty())
                .map(|item| item.chars().take(MAX_ID_LENGTH).collect::<String>())
                .take(limit)
                .collect()
        })
        .unwrap_or_default()
}

fn bounded_string(value: Option<&Value>) -> String {
    value
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| value.chars().take(MAX_ID_LENGTH).collect())
        .unwrap_or_default()
}

fn normalize_or_default(value: &str, default: &str) -> String {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        default.to_string()
    } else {
        trimmed.to_string()
    }
}

fn stable_hash_16(value: &str) -> String {
    let mut hash = 0xcbf29ce484222325u64;
    for byte in value.trim().as_bytes() {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(0x100000001b3);
    }
    format!("{hash:016x}")
}

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,
                "tree_view_state_auth_required",
                "tree view state 需要登录用户",
            )
            .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!("确保用户记录失败: {error}")))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn stable_scope_id_separates_tree_kind_and_scope() {
        let context = RequestContext::from_http_parts(
            &axum::http::Method::GET,
            &"/api/tree/view-state".parse().expect("uri"),
            &axum::http::HeaderMap::new(),
        );
        let filetree = resolve_scope(
            &context,
            "local:_mnt_Data1T_mnote",
            "local_folder",
            "filetree",
            "file:///mnt/Data1T/mnote",
            "design",
        )
        .expect("filetree scope");
        let pagetree = resolve_scope(
            &context,
            "local:_mnt_Data1T_mnote",
            "local_folder",
            "pagetree",
            "file:///mnt/Data1T/mnote",
            "design",
        )
        .expect("pagetree scope");
        assert_ne!(filetree.scope_id, pagetree.scope_id);
        assert!(filetree.scope_id.starts_with("filetree:"));
        assert!(pagetree.scope_id.starts_with("pagetree:"));
    }

    #[test]
    fn normalize_state_rejects_derived_cache() {
        let context = RequestContext::from_http_parts(
            &axum::http::Method::GET,
            &"/api/tree/view-state".parse().expect("uri"),
            &axum::http::HeaderMap::new(),
        );
        let root = std::env::current_dir().expect("cwd");
        let root_uri = format!("file://{}", root.display());
        let scope = resolve_scope(
            &context,
            "ws",
            "local_folder",
            "filetree",
            &root_uri,
            "root",
        )
        .expect("scope");
        let error = normalize_state(
            &context,
            &scope,
            json!({ "schemaVersion": 1, "rowsByParent": {} }),
        )
        .expect_err("derived cache rejected");
        assert_eq!(error.code(), "tree_view_state_contains_derived_cache");
    }
}
