use sea_orm::{ConnectionTrait, DatabaseConnection, FromQueryResult, Statement, Value};
use uuid::Uuid;

use base64::{Engine, engine::general_purpose::STANDARD as BASE64};

use crate::error::{PluginError, PluginResult};
use crate::manifest::{PluginEntity, PluginField, PluginFieldType};

/// 消毒标识符：只保留 ASCII 字母、数字、下划线，限制 63 字节（PostgreSQL NAMEDATALEN-1）
pub(crate) fn sanitize_identifier(input: &str) -> String {
    input
        .chars()
        .map(|c| {
            if c.is_ascii_alphanumeric() || c == '_' {
                c
            } else {
                '_'
            }
        })
        .take(63)
        .collect()
}

/// Schema 演进字段差异
pub struct FieldDiff {
    pub new_filterable: Vec<PluginField>,
    pub new_sortable: Vec<PluginField>,
    pub new_searchable: Vec<PluginField>,
}

/// 动态表管理器 — 处理插件动态创建/删除的数据库表
pub struct DynamicTableManager;

impl DynamicTableManager {
    /// 生成动态表名: `plugin_{sanitized_id}_{sanitized_entity}`
    pub fn table_name(plugin_id: &str, entity_name: &str) -> String {
        let sanitized_id = sanitize_identifier(plugin_id);
        let sanitized_entity = sanitize_identifier(entity_name);
        format!("plugin_{}_{}", sanitized_id, sanitized_entity)
    }

    /// 生成包含 Generated Column 的建表 DDL
    pub fn build_create_table_sql(plugin_id: &str, entity: &PluginEntity) -> String {
        let table_name = Self::table_name(plugin_id, &entity.name);

        let mut gen_cols = Vec::new();
        let mut indexes = Vec::new();

        for field in &entity.fields {
            if !field.field_type.supports_generated_column() {
                continue;
            }
            // 提取规则：unique / sortable / filterable
            let should_extract = field.unique
                || field.sortable == Some(true)
                || field.filterable == Some(true)
                || (field.required
                    && (field.sortable == Some(true) || field.filterable == Some(true)));

            if !should_extract {
                continue;
            }

            let col_name = format!("_f_{}", sanitize_identifier(&field.name));
            let sql_type = field.field_type.generated_sql_type();
            let expr = field
                .field_type
                .generated_expr(&sanitize_identifier(&field.name));

            gen_cols.push(format!(
                "    \"{}\" {} GENERATED ALWAYS AS ({}) STORED",
                col_name, sql_type, expr
            ));

            // 索引策略
            let col_idx = format!("{}_{}", sanitize_identifier(&table_name), col_name);
            if field.unique {
                indexes.push(format!(
                    "CREATE UNIQUE INDEX IF NOT EXISTS \"idx_{}_uniq\" ON \"{}\" (tenant_id, \"{}\") WHERE deleted_at IS NULL",
                    col_idx, table_name, col_name
                ));
            } else {
                indexes.push(format!(
                    "CREATE INDEX IF NOT EXISTS \"idx_{}\" ON \"{}\" (tenant_id, \"{}\") WHERE deleted_at IS NULL",
                    col_idx, table_name, col_name
                ));
            }
        }

        // pg_trgm 索引
        for field in &entity.fields {
            if field.searchable == Some(true) && matches!(field.field_type, PluginFieldType::String)
            {
                let sf = sanitize_identifier(&field.name);
                indexes.push(format!(
                    "CREATE INDEX IF NOT EXISTS \"idx_{}_{}_trgm\" ON \"{}\" USING GIN ((data->>'{}') gin_trgm_ops) WHERE deleted_at IS NULL",
                    sanitize_identifier(&table_name), sf, table_name, sf
                ));
            }
        }

        // 覆盖索引
        indexes.push(format!(
            "CREATE INDEX IF NOT EXISTS \"idx_{}_tenant_cover\" ON \"{}\" (tenant_id, created_at DESC) INCLUDE (id, data, updated_at, version) WHERE deleted_at IS NULL",
            sanitize_identifier(&table_name), table_name
        ));

        let gen_cols_sql = if gen_cols.is_empty() {
            String::new()
        } else {
            format!(",\n{}", gen_cols.join(",\n"))
        };

        format!(
            "CREATE TABLE IF NOT EXISTS \"{}\" (\
             \"id\" UUID PRIMARY KEY DEFAULT gen_random_uuid(), \
             \"tenant_id\" UUID NOT NULL, \
             \"data\" JSONB NOT NULL DEFAULT '{{}}'{gen_cols}, \
             \"created_at\" TIMESTAMPTZ NOT NULL DEFAULT NOW(), \
             \"updated_at\" TIMESTAMPTZ NOT NULL DEFAULT NOW(), \
             \"created_by\" UUID, \
             \"updated_by\" UUID, \
             \"deleted_at\" TIMESTAMPTZ, \
             \"version\" INT NOT NULL DEFAULT 1);\n\
             {}",
            table_name,
            indexes.join(";\n"),
            gen_cols = gen_cols_sql,
        )
    }

    /// 创建动态表（使用 Generated Column + pg_trgm + 覆盖索引）
    pub async fn create_table(
        db: &DatabaseConnection,
        plugin_id: &str,
        entity: &PluginEntity,
    ) -> PluginResult<()> {
        let ddl = Self::build_create_table_sql(plugin_id, entity);
        for sql in ddl.split(';').map(|s| s.trim()).filter(|s| !s.is_empty()) {
            tracing::info!(sql = %sql, "Executing DDL");
            db.execute_unprepared(sql).await.map_err(|e| {
                tracing::error!(sql = %sql, error = %e, "DDL execution failed");
                PluginError::DatabaseError(e.to_string())
            })?;
        }
        tracing::info!(
            plugin_id = %plugin_id,
            entity = %entity.name,
            "Dynamic table created with Generated Columns"
        );
        Ok(())
    }

    /// 删除动态表
    pub async fn drop_table(
        db: &DatabaseConnection,
        plugin_id: &str,
        entity_name: &str,
    ) -> PluginResult<()> {
        let table_name = Self::table_name(plugin_id, entity_name);
        let sql = format!("DROP TABLE IF EXISTS \"{}\"", table_name);
        db.execute(Statement::from_string(
            sea_orm::DatabaseBackend::Postgres,
            sql,
        ))
        .await
        .map_err(|e| PluginError::DatabaseError(e.to_string()))?;
        tracing::info!(table = %table_name, "Dynamic table dropped");
        Ok(())
    }

    /// Schema 演进：对比新旧实体字段，返回需要新增 Generated Column 的差异
    pub fn diff_entity_fields(old: &PluginEntity, new: &PluginEntity) -> FieldDiff {
        let old_names: std::collections::HashSet<String> =
            old.fields.iter().map(|f| f.name.clone()).collect();

        let mut new_filterable = Vec::new();
        let mut new_sortable = Vec::new();
        let mut new_searchable = Vec::new();

        for field in &new.fields {
            if old_names.contains(&field.name) {
                continue;
            }
            // 新增字段 + 需要 Generated Column 的条件
            let needs_gen =
                field.unique || field.sortable == Some(true) || field.filterable == Some(true);
            if needs_gen {
                new_filterable.push(field.clone());
                if field.sortable == Some(true) {
                    new_sortable.push(field.clone());
                }
            }
            if field.searchable == Some(true) && matches!(field.field_type, PluginFieldType::String)
            {
                new_searchable.push(field.clone());
            }
        }

        FieldDiff {
            new_filterable,
            new_sortable,
            new_searchable,
        }
    }

    /// Schema 演进：为已有实体新增 Generated Column 和索引
    pub async fn alter_add_generated_columns(
        db: &DatabaseConnection,
        plugin_id: &str,
        entity: &PluginEntity,
        diff: &FieldDiff,
    ) -> PluginResult<()> {
        let table_name = Self::table_name(plugin_id, &entity.name);
        let mut statements = Vec::new();

        for field in &diff.new_filterable {
            if !field.field_type.supports_generated_column() {
                continue;
            }
            let col_name = format!("_f_{}", sanitize_identifier(&field.name));
            let sql_type = field.field_type.generated_sql_type();
            let expr = field
                .field_type
                .generated_expr(&sanitize_identifier(&field.name));
            let _safe_field = sanitize_identifier(&field.name);

            statements.push(format!(
                "ALTER TABLE \"{}\" ADD COLUMN IF NOT EXISTS \"{}\" {} GENERATED ALWAYS AS ({}) STORED",
                table_name, col_name, sql_type, expr
            ));

            let col_idx = format!("{}_{}", sanitize_identifier(&table_name), col_name);
            if field.unique {
                statements.push(format!(
                    "CREATE UNIQUE INDEX IF NOT EXISTS \"idx_{}_uniq\" ON \"{}\" (tenant_id, \"{}\") WHERE deleted_at IS NULL",
                    col_idx, table_name, col_name
                ));
            } else {
                statements.push(format!(
                    "CREATE INDEX IF NOT EXISTS \"idx_{}\" ON \"{}\" (tenant_id, \"{}\") WHERE deleted_at IS NULL",
                    col_idx, table_name, col_name
                ));
            }
        }

        for field in &diff.new_searchable {
            let sf = sanitize_identifier(&field.name);
            let col_name = format!("_f_{}", sf);
            let col_idx = format!("{}_{}trgm", sanitize_identifier(&table_name), col_name);
            statements.push(format!(
                "CREATE INDEX IF NOT EXISTS \"idx_{}\" ON \"{}\" USING gin (\"{}\" gin_trgm_ops) WHERE deleted_at IS NULL AND \"{}\" IS NOT NULL",
                col_idx, table_name, col_name, col_name
            ));
        }

        for sql in &statements {
            tracing::info!(sql = %sql, "Executing ALTER TABLE");
            db.execute_unprepared(sql).await.map_err(|e| {
                tracing::error!(sql = %sql, error = %e, "ALTER TABLE failed");
                PluginError::DatabaseError(e.to_string())
            })?;
        }

        tracing::info!(
            table = %table_name,
            added_columns = diff.new_filterable.len(),
            added_search_indexes = diff.new_searchable.len(),
            "Schema evolution: Generated Columns added"
        );
        Ok(())
    }

    /// 检查表是否存在
    pub async fn table_exists(db: &DatabaseConnection, table_name: &str) -> PluginResult<bool> {
        #[derive(FromQueryResult)]
        struct ExistsResult {
            exists: bool,
        }
        let result = ExistsResult::find_by_statement(Statement::from_sql_and_values(
            sea_orm::DatabaseBackend::Postgres,
            "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1)",
            [table_name.into()],
        ))
        .one(db)
        .await
        .map_err(|e| PluginError::DatabaseError(e.to_string()))?;

        Ok(result.map(|r| r.exists).unwrap_or(false))
    }

    /// 构建 INSERT SQL
    pub fn build_insert_sql(
        table_name: &str,
        tenant_id: Uuid,
        user_id: Uuid,
        data: &serde_json::Value,
    ) -> (String, Vec<Value>) {
        let id = Uuid::now_v7();
        Self::build_insert_sql_with_id(table_name, id, tenant_id, user_id, data)
    }

    /// 构建 INSERT SQL（指定 ID）
    pub fn build_insert_sql_with_id(
        table_name: &str,
        id: Uuid,
        tenant_id: Uuid,
        user_id: Uuid,
        data: &serde_json::Value,
    ) -> (String, Vec<Value>) {
        let sql = format!(
            "INSERT INTO \"{}\" (id, tenant_id, data, created_by, updated_by, version) \
             VALUES ($1, $2, $3::jsonb, $4, $5, 1) \
             RETURNING id, tenant_id, data, created_at, updated_at, version",
            table_name
        );
        let values = vec![
            id.into(),
            tenant_id.into(),
            serde_json::to_string(data).unwrap_or_default().into(),
            user_id.into(),
            user_id.into(),
        ];
        (sql, values)
    }

    /// 构建 SELECT SQL
    pub fn build_query_sql(
        table_name: &str,
        tenant_id: Uuid,
        limit: u64,
        offset: u64,
    ) -> (String, Vec<Value>) {
        let sql = format!(
            "SELECT id, data, created_at, updated_at, version \
             FROM \"{}\" \
             WHERE tenant_id = $1 AND deleted_at IS NULL \
             ORDER BY created_at DESC \
             LIMIT $2 OFFSET $3",
            table_name
        );
        let values = vec![
            tenant_id.into(),
            (limit as i64).into(),
            (offset as i64).into(),
        ];
        (sql, values)
    }

    /// 构建 COUNT SQL
    pub fn build_count_sql(table_name: &str, tenant_id: Uuid) -> (String, Vec<Value>) {
        let sql = format!(
            "SELECT COUNT(*) as count FROM \"{}\" WHERE tenant_id = $1 AND deleted_at IS NULL",
            table_name
        );
        let values = vec![tenant_id.into()];
        (sql, values)
    }

    /// 构建 UPDATE SQL（含乐观锁）
    pub fn build_update_sql(
        table_name: &str,
        id: Uuid,
        tenant_id: Uuid,
        user_id: Uuid,
        data: &serde_json::Value,
        version: i32,
    ) -> (String, Vec<Value>) {
        let sql = format!(
            "UPDATE \"{}\" \
             SET data = $1::jsonb, updated_at = NOW(), updated_by = $2, version = version + 1 \
             WHERE id = $3 AND tenant_id = $4 AND version = $5 AND deleted_at IS NULL \
             RETURNING id, data, created_at, updated_at, version",
            table_name
        );
        let values = vec![
            serde_json::to_string(data).unwrap_or_default().into(),
            user_id.into(),
            id.into(),
            tenant_id.into(),
            version.into(),
        ];
        (sql, values)
    }

    /// 构建 PATCH SQL — 只更新 data 中提供的字段，未提供的保持不变
    /// 使用 jsonb_set 逐层合并，实现部分更新
    pub fn build_patch_sql(
        table_name: &str,
        id: Uuid,
        tenant_id: Uuid,
        user_id: Uuid,
        partial_data: serde_json::Value,
        version: i32,
    ) -> (String, Vec<Value>) {
        let mut set_expr = "data".to_string();
        if let Some(obj) = partial_data.as_object() {
            for key in obj.keys() {
                let clean_key = sanitize_identifier(key);
                set_expr = format!(
                    "jsonb_set({}, '{{{}}}', $1::jsonb->'{}', true)",
                    set_expr, clean_key, clean_key
                );
            }
        }

        let sql = format!(
            "UPDATE \"{}\" \
             SET data = {}, updated_at = NOW(), updated_by = $2, version = version + 1 \
             WHERE id = $3 AND tenant_id = $4 AND version = $5 AND deleted_at IS NULL \
             RETURNING id, data, created_at, updated_at, version",
            table_name, set_expr
        );
        let values = vec![
            serde_json::to_string(&partial_data)
                .unwrap_or_default()
                .into(),
            user_id.into(),
            id.into(),
            tenant_id.into(),
            version.into(),
        ];
        (sql, values)
    }

    /// 构建 DELETE SQL（软删除）
    pub fn build_delete_sql(table_name: &str, id: Uuid, tenant_id: Uuid) -> (String, Vec<Value>) {
        let sql = format!(
            "UPDATE \"{}\" \
             SET deleted_at = NOW(), updated_at = NOW() \
             WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL",
            table_name
        );
        let values = vec![id.into(), tenant_id.into()];
        (sql, values)
    }

    /// 构建单条查询 SQL
    pub fn build_get_by_id_sql(
        table_name: &str,
        id: Uuid,
        tenant_id: Uuid,
    ) -> (String, Vec<Value>) {
        let sql = format!(
            "SELECT id, data, created_at, updated_at, version \
             FROM \"{}\" \
             WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL",
            table_name
        );
        let values = vec![id.into(), tenant_id.into()];
        (sql, values)
    }

    /// 构建唯一索引 SQL（供测试验证）
    pub fn build_unique_index_sql(table_name: &str, field_name: &str) -> String {
        let sanitized_field = sanitize_identifier(field_name);
        let idx_name = format!(
            "idx_{}_{}_uniq",
            sanitize_identifier(table_name),
            sanitized_field
        );
        format!(
            "CREATE UNIQUE INDEX IF NOT EXISTS \"{}\" ON \"{}\" (\"data\"->>'{}') WHERE \"deleted_at\" IS NULL",
            idx_name, table_name, sanitized_field
        )
    }

    /// 构建带过滤条件的 COUNT SQL
    /// 复用 build_filtered_query_sql 的条件构建逻辑，但只做 COUNT
    pub fn build_filtered_count_sql(
        table_name: &str,
        tenant_id: Uuid,
        filter: Option<serde_json::Value>,
        search: Option<(String, String)>, // (searchable_fields_csv, keyword)
    ) -> Result<(String, Vec<Value>), String> {
        let mut conditions = vec![
            format!("\"tenant_id\" = ${}", 1),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut param_idx = 2;
        let mut values: Vec<Value> = vec![tenant_id.into()];

        // 处理 filter（与 build_filtered_query_sql 保持一致）
        if let Some(f) = filter
            && let Some(obj) = f.as_object()
        {
            for (key, val) in obj {
                let clean_key = sanitize_identifier(key);
                if clean_key.is_empty() {
                    return Err(format!("无效的过滤字段名: {}", key));
                }
                conditions.push(format!("\"data\"->>'{}' = ${}", clean_key, param_idx));
                values.push(Value::String(Some(Box::new(
                    val.as_str().unwrap_or("").to_string(),
                ))));
                param_idx += 1;
            }
        }

        // 处理 search（与 build_filtered_query_sql 保持一致）
        if let Some((fields_csv, keyword)) = search {
            let escaped = keyword.replace('%', "\\%").replace('_', "\\_");
            let fields: Vec<&str> = fields_csv.split(',').collect();
            let search_param_idx = param_idx;
            let search_conditions: Vec<String> = fields
                .iter()
                .map(|f| {
                    let clean = sanitize_identifier(f.trim());
                    format!("\"data\"->>'{}' ILIKE ${}", clean, search_param_idx)
                })
                .collect();
            conditions.push(format!("({})", search_conditions.join(" OR ")));
            values.push(Value::String(Some(Box::new(format!("%{}%", escaped)))));
        }

        let sql = format!(
            "SELECT COUNT(*) as count FROM \"{}\" WHERE {}",
            table_name,
            conditions.join(" AND "),
        );

        Ok((sql, values))
    }

    /// 构建聚合查询 SQL — 按 JSONB 字段分组计数
    /// SELECT data->>'group_field' as key, COUNT(*) as count
    /// FROM table WHERE tenant_id = $1 AND deleted_at IS NULL [AND filter...]
    /// GROUP BY data->>'group_field' ORDER BY count DESC
    pub fn build_aggregate_sql(
        table_name: &str,
        tenant_id: Uuid,
        group_by_field: &str,
        filter: Option<serde_json::Value>,
    ) -> Result<(String, Vec<Value>), String> {
        let clean_group = sanitize_identifier(group_by_field);
        if clean_group.is_empty() {
            return Err(format!("无效的分组字段名: {}", group_by_field));
        }

        let mut conditions = vec![
            format!("\"tenant_id\" = ${}", 1),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut param_idx = 2;
        let mut values: Vec<Value> = vec![tenant_id.into()];

        // 处理 filter
        if let Some(f) = filter
            && let Some(obj) = f.as_object()
        {
            for (key, val) in obj {
                let clean_key = sanitize_identifier(key);
                if clean_key.is_empty() {
                    return Err(format!("无效的过滤字段名: {}", key));
                }
                conditions.push(format!("\"data\"->>'{}' = ${}", clean_key, param_idx));
                values.push(Value::String(Some(Box::new(
                    val.as_str().unwrap_or("").to_string(),
                ))));
                param_idx += 1;
            }
        }

        let sql = format!(
            "SELECT \"data\"->>'{}' as key, COUNT(*) as count \
             FROM \"{}\" \
             WHERE {} \
             GROUP BY \"data\"->>'{}' \
             ORDER BY count DESC",
            clean_group,
            table_name,
            conditions.join(" AND "),
            clean_group,
        );

        Ok((sql, values))
    }

    /// 构建多聚合函数 SQL（支持 COUNT/SUM/AVG/MIN/MAX）
    pub fn build_aggregate_multi_sql(
        table_name: &str,
        tenant_id: Uuid,
        group_by_field: &str,
        aggregations: &[(String, String)], // (func, field) e.g. ("sum", "amount")
        filter: Option<serde_json::Value>,
    ) -> Result<(String, Vec<Value>), String> {
        let clean_group = sanitize_identifier(group_by_field);
        if clean_group.is_empty() {
            return Err(format!("无效的分组字段名: {}", group_by_field));
        }

        let mut conditions = vec![
            format!("\"tenant_id\" = ${}", 1),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut param_idx = 2;
        let mut values: Vec<Value> = vec![tenant_id.into()];

        if let Some(f) = filter
            && let Some(obj) = f.as_object()
        {
            for (key, val) in obj {
                let clean_key = sanitize_identifier(key);
                if clean_key.is_empty() {
                    return Err(format!("无效的过滤字段名: {}", key));
                }
                conditions.push(format!("\"data\"->>'{}' = ${}", clean_key, param_idx));
                values.push(Value::String(Some(Box::new(
                    val.as_str().unwrap_or("").to_string(),
                ))));
                param_idx += 1;
            }
        }

        let mut select_parts = vec![
            format!("\"_f_{}\" as key", clean_group),
            "COUNT(*) as count".to_string(),
        ];

        for (func, field) in aggregations {
            let clean_field = sanitize_identifier(field);
            let func_lower = func.to_lowercase();
            match func_lower.as_str() {
                "sum" => select_parts.push(format!(
                    "COALESCE(SUM(\"_f_{}\"), 0) as sum_{}",
                    clean_field, clean_field
                )),
                "avg" => select_parts.push(format!(
                    "COALESCE(AVG(\"_f_{}\"), 0) as avg_{}",
                    clean_field, clean_field
                )),
                "min" => select_parts.push(format!(
                    "MIN(\"_f_{}\") as min_{}",
                    clean_field, clean_field
                )),
                "max" => select_parts.push(format!(
                    "MAX(\"_f_{}\") as max_{}",
                    clean_field, clean_field
                )),
                _ => {}
            }
        }

        let sql = format!(
            "SELECT {} \
             FROM \"{}\" \
             WHERE {} \
             GROUP BY \"_f_{}\" \
             ORDER BY count DESC",
            select_parts.join(", "),
            table_name,
            conditions.join(" AND "),
            clean_group,
        );

        Ok((sql, values))
    }

    /// 构建带过滤条件的查询 SQL
    #[allow(clippy::too_many_arguments)]
    pub fn build_filtered_query_sql(
        table_name: &str,
        tenant_id: Uuid,
        limit: u64,
        offset: u64,
        filter: Option<serde_json::Value>,
        search: Option<(String, String)>, // (searchable_fields_csv, keyword)
        sort_by: Option<String>,
        sort_order: Option<String>,
    ) -> Result<(String, Vec<Value>), String> {
        let mut conditions = vec![
            format!("\"tenant_id\" = ${}", 1),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut param_idx = 2;
        let mut values: Vec<Value> = vec![tenant_id.into()];

        // 处理 filter
        if let Some(f) = filter
            && let Some(obj) = f.as_object()
        {
            for (key, val) in obj {
                let clean_key = sanitize_identifier(key);
                if clean_key.is_empty() {
                    return Err(format!("无效的过滤字段名: {}", key));
                }
                conditions.push(format!("\"data\"->>'{}' = ${}", clean_key, param_idx));
                values.push(Value::String(Some(Box::new(
                    val.as_str().unwrap_or("").to_string(),
                ))));
                param_idx += 1;
            }
        }

        // 处理 search — 所有 searchable 字段共享同一个 ILIKE 参数
        if let Some((fields_csv, keyword)) = search {
            let escaped = keyword.replace('%', "\\%").replace('_', "\\_");
            let fields: Vec<&str> = fields_csv.split(',').collect();
            let search_param_idx = param_idx;
            let search_conditions: Vec<String> = fields
                .iter()
                .map(|f| {
                    let clean = sanitize_identifier(f.trim());
                    format!("\"data\"->>'{}' ILIKE ${}", clean, search_param_idx)
                })
                .collect();
            conditions.push(format!("({})", search_conditions.join(" OR ")));
            values.push(Value::String(Some(Box::new(format!("%{}%", escaped)))));
            param_idx += 1;
        }

        // 处理 sort
        let order_clause = if let Some(sb) = sort_by {
            let clean = sanitize_identifier(&sb);
            if clean.is_empty() {
                return Err(format!("无效的排序字段名: {}", sb));
            }
            let dir = match sort_order.as_deref() {
                Some("asc") | Some("ASC") => "ASC",
                _ => "DESC",
            };
            format!("ORDER BY \"data\"->>'{}' {}", clean, dir)
        } else {
            "ORDER BY \"created_at\" DESC".to_string()
        };

        let sql = format!(
            "SELECT id, data, created_at, updated_at, version FROM \"{}\" WHERE {} {} LIMIT ${} OFFSET ${}",
            table_name,
            conditions.join(" AND "),
            order_clause,
            param_idx,
            param_idx + 1,
        );
        values.push((limit as i64).into());
        values.push((offset as i64).into());

        Ok((sql, values))
    }

    /// 返回字段引用函数 — Generated Column 存在时用 _f_{name}
    pub fn field_reference_fn(generated_fields: &[String]) -> impl Fn(&str) -> String + '_ {
        move |field_name: &str| {
            let clean = sanitize_identifier(field_name);
            if generated_fields.contains(&clean) {
                format!("\"_f_{}\"", clean)
            } else {
                format!("\"data\"->>'{}'", clean)
            }
        }
    }

    /// 扩展版查询构建 — 支持 Generated Column 路由
    #[allow(clippy::too_many_arguments)]
    pub fn build_filtered_query_sql_ex(
        table_name: &str,
        tenant_id: Uuid,
        limit: u64,
        offset: u64,
        filter: Option<serde_json::Value>,
        search: Option<(String, String)>,
        sort_by: Option<String>,
        sort_order: Option<String>,
        generated_fields: &[String],
    ) -> Result<(String, Vec<Value>), String> {
        let ref_fn = Self::field_reference_fn(generated_fields);

        let mut conditions = vec![
            format!("\"tenant_id\" = ${}", 1),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut param_idx = 2;
        let mut values: Vec<Value> = vec![tenant_id.into()];

        // filter
        if let Some(f) = filter
            && let Some(obj) = f.as_object()
        {
            for (key, val) in obj {
                let clean_key = sanitize_identifier(key);
                if clean_key.is_empty() {
                    return Err(format!("无效的过滤字段名: {}", key));
                }
                conditions.push(format!("{} = ${}", ref_fn(&clean_key), param_idx));
                values.push(Value::String(Some(Box::new(
                    val.as_str().unwrap_or("").to_string(),
                ))));
                param_idx += 1;
            }
        }

        // search
        if let Some((fields_csv, keyword)) = search {
            let escaped = keyword.replace('%', "\\%").replace('_', "\\_");
            let fields: Vec<&str> = fields_csv.split(',').collect();
            let search_param_idx = param_idx;
            let search_conditions: Vec<String> = fields
                .iter()
                .map(|f| {
                    let clean = sanitize_identifier(f.trim());
                    format!("\"data\"->>'{}' ILIKE ${}", clean, search_param_idx)
                })
                .collect();
            conditions.push(format!("({})", search_conditions.join(" OR ")));
            values.push(Value::String(Some(Box::new(format!("%{}%", escaped)))));
            param_idx += 1;
        }

        // sort
        let order_clause = if let Some(sb) = sort_by {
            let clean = sanitize_identifier(&sb);
            if clean.is_empty() {
                return Err(format!("无效的排序字段名: {}", sb));
            }
            let dir = match sort_order.as_deref() {
                Some("asc") | Some("ASC") => "ASC",
                _ => "DESC",
            };
            format!("ORDER BY {} {}", ref_fn(&clean), dir)
        } else {
            "ORDER BY \"created_at\" DESC".to_string()
        };

        let sql = format!(
            "SELECT id, data, created_at, updated_at, version FROM \"{}\" WHERE {} {} LIMIT ${} OFFSET ${}",
            table_name,
            conditions.join(" AND "),
            order_clause,
            param_idx,
            param_idx + 1,
        );
        values.push((limit as i64).into());
        values.push((offset as i64).into());

        Ok((sql, values))
    }

    /// 构建数据范围 SQL 条件 — 用于行级数据权限过滤
    ///
    /// 根据权限范围级别 (scope_level) 生成对应的 WHERE 子句和参数：
    /// - "all": 无额外条件（空字符串）
    /// - "self": 只能看自己创建/拥有的数据
    /// - "department" / "department_tree": 能看部门成员的数据
    ///
    /// 返回 (sql_fragment, params)，sql_fragment 可直接拼接到 WHERE 子句中
    pub fn build_data_scope_condition_with_params(
        scope_level: &str,
        current_user_id: &Uuid,
        owner_field: &str,
        dept_member_ids: &[Uuid],
        start_param_idx: usize,
        generated_fields: &[String],
    ) -> (String, Vec<Value>) {
        let ref_fn = Self::field_reference_fn(generated_fields);
        let owner_ref = ref_fn(owner_field);
        match scope_level {
            "self" => (
                format!(
                    "({} = ${} OR \"created_by\" = ${})",
                    owner_ref, start_param_idx, start_param_idx
                ),
                vec![
                    current_user_id.to_string().into(),
                    (*current_user_id).into(),
                ],
            ),
            "department" | "department_tree" => {
                if dept_member_ids.is_empty() {
                    // 部门成员为空时退化为 self 范围
                    (
                        format!(
                            "({} = ${} OR \"created_by\" = ${})",
                            owner_ref, start_param_idx, start_param_idx
                        ),
                        vec![
                            current_user_id.to_string().into(),
                            (*current_user_id).into(),
                        ],
                    )
                } else {
                    let placeholders: Vec<String> = dept_member_ids
                        .iter()
                        .enumerate()
                        .map(|(i, _)| format!("${}", start_param_idx + i))
                        .collect();
                    let values: Vec<Value> = dept_member_ids
                        .iter()
                        .map(|id| id.to_string().into())
                        .collect();
                    (
                        format!("{} IN ({})", owner_ref, placeholders.join(", ")),
                        values,
                    )
                }
            }
            "all" => (String::new(), vec![]),
            _ => (String::new(), vec![]),
        }
    }

    /// 编码游标
    pub fn encode_cursor(values: &[String], id: &Uuid) -> String {
        let obj = serde_json::json!({
            "v": values,
            "id": id.to_string(),
        });
        BASE64.encode(obj.to_string())
    }

    /// 解码游标
    pub fn decode_cursor(cursor: &str) -> Result<(Vec<String>, Uuid), String> {
        let json_str = BASE64
            .decode(cursor)
            .map_err(|e| format!("游标 Base64 解码失败: {}", e))?;
        let obj: serde_json::Value =
            serde_json::from_slice(&json_str).map_err(|e| format!("游标 JSON 解析失败: {}", e))?;
        let values = obj["v"]
            .as_array()
            .ok_or("游标缺少 v 字段")?
            .iter()
            .map(|v| v.as_str().unwrap_or("").to_string())
            .collect();
        let id = obj["id"].as_str().ok_or("游标缺少 id 字段")?;
        let id = Uuid::parse_str(id).map_err(|e| format!("游标 id 解析失败: {}", e))?;
        Ok((values, id))
    }

    /// 构建 Keyset 分页 SQL
    pub fn build_keyset_query_sql(
        table_name: &str,
        tenant_id: Uuid,
        limit: u64,
        cursor: Option<String>,
        sort_column: Option<String>,
        sort_direction: &str,
        generated_fields: &[String],
    ) -> Result<(String, Vec<Value>), String> {
        let dir = match sort_direction {
            "ASC" => "ASC",
            _ => "DESC",
        };
        let ref_fn = Self::field_reference_fn(generated_fields);
        let sort_col = sort_column
            .as_deref()
            .map(ref_fn)
            .unwrap_or("\"created_at\"".to_string());

        let mut values: Vec<Value> = vec![tenant_id.into()];
        let mut param_idx = 2;

        let cursor_condition = if let Some(c) = cursor {
            let (sort_vals, cursor_id) = Self::decode_cursor(&c)?;
            let cond = format!(
                "ROW({}, \"id\") {} (${}, ${})",
                sort_col,
                if dir == "ASC" { ">" } else { "<" },
                param_idx,
                param_idx + 1
            );
            values.push(Value::String(Some(Box::new(
                sort_vals.first().cloned().unwrap_or_default(),
            ))));
            values.push(cursor_id.into());
            param_idx += 2;
            Some(cond)
        } else {
            None
        };

        let where_extra = cursor_condition
            .map(|c| format!(" AND {}", c))
            .unwrap_or_default();

        let sql = format!(
            "SELECT id, data, created_at, updated_at, version FROM \"{}\" \
             WHERE \"tenant_id\" = $1 AND \"deleted_at\" IS NULL{} \
             ORDER BY {}, \"id\" {} LIMIT ${}",
            table_name, where_extra, sort_col, dir, param_idx,
        );
        values.push((limit as i64).into());

        Ok((sql, values))
    }

    /// 构建时间序列查询 SQL
    pub fn build_timeseries_sql(
        table_name: &str,
        tenant_id: Uuid,
        time_field: &str,
        time_grain: &str,
        start: Option<&str>,
        end: Option<&str>,
    ) -> Result<(String, Vec<Value>), String> {
        let clean_field = sanitize_identifier(time_field);
        let grain = match time_grain {
            "day" | "week" | "month" => time_grain,
            _ => return Err(format!("不支持的 time_grain: {}", time_grain)),
        };

        let mut conditions = vec![
            "\"tenant_id\" = $1".to_string(),
            "\"deleted_at\" IS NULL".to_string(),
        ];
        let mut values: Vec<Value> = vec![tenant_id.into()];
        let mut param_idx = 2;

        if let Some(s) = start {
            conditions.push(format!(
                "(data->>'{}')::timestamp >= ${}",
                clean_field, param_idx
            ));
            values.push(Value::String(Some(Box::new(s.to_string()))));
            param_idx += 1;
        }
        if let Some(e) = end {
            conditions.push(format!(
                "(data->>'{}')::timestamp < ${}",
                clean_field, param_idx
            ));
            values.push(Value::String(Some(Box::new(e.to_string()))));
        }

        let sql = format!(
            "SELECT to_char(date_trunc('{}', (data->>'{}')::timestamp), 'YYYY-MM-DD') as period, \
             COUNT(*) as count \
             FROM \"{}\" WHERE {} \
             GROUP BY date_trunc('{}', (data->>'{}')::timestamp) \
             ORDER BY period",
            grain,
            clean_field,
            table_name,
            conditions.join(" AND "),
            grain,
            clean_field,
        );

        Ok((sql, values))
    }
}

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

    #[test]
    fn test_unique_index_sql_uses_create_unique_index() {
        let sql = DynamicTableManager::build_unique_index_sql("plugin_test", "code");
        assert!(
            sql.contains("CREATE UNIQUE INDEX"),
            "Expected UNIQUE index, got: {}",
            sql
        );
    }

    #[test]
    fn test_build_filtered_query_sql_with_filter() {
        let (sql, values) = DynamicTableManager::build_filtered_query_sql(
            "plugin_test_customer",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            Some(serde_json::json!({"customer_id": "abc-123"})),
            None,
            None,
            None,
        )
        .unwrap();
        assert!(
            sql.contains("\"data\"->>'customer_id' ="),
            "Expected filter in SQL, got: {}",
            sql
        );
        assert!(sql.contains("tenant_id"), "Expected tenant_id filter");
        // 验证参数值
        assert_eq!(values.len(), 4); // tenant_id + filter_value + limit + offset
    }

    #[test]
    fn test_build_filtered_query_sql_sanitizes_keys() {
        let result = DynamicTableManager::build_filtered_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            Some(serde_json::json!({"evil'; DROP TABLE--": "value"})),
            None,
            None,
            None,
        );
        // 恶意 key 被清理为合法标识符
        let (sql, _) = result.unwrap();
        assert!(
            !sql.contains("DROP TABLE"),
            "SQL should not contain injection: {}",
            sql
        );
        assert!(
            sql.contains("evil___DROP_TABLE__"),
            "Key should be sanitized: {}",
            sql
        );
    }

    #[test]
    fn test_build_filtered_query_sql_with_search() {
        let (sql, values) = DynamicTableManager::build_filtered_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            None,
            Some(("name,code".to_string(), "测试关键词".to_string())),
            None,
            None,
        )
        .unwrap();
        assert!(sql.contains("ILIKE"), "Expected ILIKE in SQL, got: {}", sql);
        // 验证搜索参数值包含 %...%
        if let Value::String(Some(s)) = &values[1] {
            assert!(
                s.contains("测试关键词"),
                "Search value should contain keyword"
            );
            assert!(s.starts_with('%'), "Search value should start with %");
        }
    }

    #[test]
    fn test_build_filtered_query_sql_with_sort() {
        let (sql, _) = DynamicTableManager::build_filtered_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            None,
            None,
            Some("name".to_string()),
            Some("asc".to_string()),
        )
        .unwrap();
        assert!(
            sql.contains("ORDER BY \"data\"->>'name' ASC"),
            "Expected sort clause, got: {}",
            sql
        );
    }

    #[test]
    fn test_build_filtered_query_sql_default_sort() {
        let (sql, _) = DynamicTableManager::build_filtered_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            None,
            None,
            None,
            None,
        )
        .unwrap();
        assert!(
            sql.contains("ORDER BY \"created_at\" DESC"),
            "Expected default sort, got: {}",
            sql
        );
    }

    // ===== build_patch_sql 测试 =====

    #[test]
    fn test_build_patch_sql_merges_fields() {
        let (sql, values) = DynamicTableManager::build_patch_sql(
            "plugin_test_customer",
            Uuid::parse_str("00000000-0000-0000-0000-000000000099").unwrap(),
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            Uuid::parse_str("00000000-0000-0000-0000-000000000050").unwrap(),
            serde_json::json!({"level": "vip", "status": "active"}),
            3,
        );
        assert!(sql.contains("jsonb_set"), "PATCH 应使用 jsonb_set 合并");
        assert!(sql.contains("version = version + 1"), "PATCH 应更新版本号");
        assert!(sql.contains("WHERE id = $3"), "应有 id 条件");
        assert!(sql.contains("version = $5"), "应有乐观锁");
        assert_eq!(values.len(), 5, "应有 5 个参数");
    }

    // ===== build_filtered_count_sql 测试 =====

    #[test]
    fn test_build_filtered_count_sql_basic() {
        let (sql, values) = DynamicTableManager::build_filtered_count_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            None,
            None,
        )
        .unwrap();
        assert!(sql.contains("COUNT(*)"), "Expected COUNT(*), got: {}", sql);
        assert!(sql.contains("tenant_id"), "Expected tenant_id filter");
        assert!(sql.contains("deleted_at"), "Expected soft delete filter");
        assert_eq!(values.len(), 1); // 仅 tenant_id
    }

    #[test]
    fn test_build_filtered_count_sql_with_filter() {
        let (sql, values) = DynamicTableManager::build_filtered_count_sql(
            "plugin_test_customer",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            Some(serde_json::json!({"status": "active"})),
            None,
        )
        .unwrap();
        assert!(
            sql.contains("\"data\"->>'status' ="),
            "Expected filter, got: {}",
            sql
        );
        assert_eq!(values.len(), 2); // tenant_id + filter_value
    }

    #[test]
    fn test_build_filtered_count_sql_with_search() {
        let (sql, values) = DynamicTableManager::build_filtered_count_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            None,
            Some(("name,code".to_string(), "搜索词".to_string())),
        )
        .unwrap();
        assert!(sql.contains("ILIKE"), "Expected ILIKE, got: {}", sql);
        assert_eq!(values.len(), 2); // tenant_id + search_param
    }

    #[test]
    fn test_build_filtered_count_sql_no_limit_offset() {
        // COUNT SQL 不应包含 LIMIT/OFFSET
        let (sql, _) = DynamicTableManager::build_filtered_count_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            None,
            None,
        )
        .unwrap();
        assert!(!sql.contains("LIMIT"), "COUNT SQL 不应包含 LIMIT");
        assert!(!sql.contains("OFFSET"), "COUNT SQL 不应包含 OFFSET");
    }

    // ===== build_aggregate_sql 测试 =====

    #[test]
    fn test_build_aggregate_sql_basic() {
        let (sql, values) = DynamicTableManager::build_aggregate_sql(
            "plugin_test_customer",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "status",
            None,
        )
        .unwrap();
        assert!(sql.contains("GROUP BY"), "Expected GROUP BY, got: {}", sql);
        assert!(
            sql.contains("\"data\"->>'status'"),
            "Expected group field, got: {}",
            sql
        );
        assert!(
            sql.contains("ORDER BY count DESC"),
            "Expected ORDER BY count DESC, got: {}",
            sql
        );
        assert_eq!(values.len(), 1); // 仅 tenant_id
    }

    #[test]
    fn test_build_aggregate_sql_with_filter() {
        let (sql, values) = DynamicTableManager::build_aggregate_sql(
            "plugin_test_customer",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "region",
            Some(serde_json::json!({"status": "active"})),
        )
        .unwrap();
        assert!(
            sql.contains("\"data\"->>'region'"),
            "Expected group field, got: {}",
            sql
        );
        assert!(
            sql.contains("\"data\"->>'status' ="),
            "Expected filter, got: {}",
            sql
        );
        assert_eq!(values.len(), 2); // tenant_id + filter_value
    }

    #[test]
    fn test_build_aggregate_sql_sanitizes_group_field() {
        let result = DynamicTableManager::build_aggregate_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "evil'; DROP TABLE--",
            None,
        );
        let (sql, _) = result.unwrap();
        assert!(!sql.contains("DROP TABLE"), "SQL 不应包含注入: {}", sql);
        assert!(
            sql.contains("evil___DROP_TABLE__"),
            "字段名应被清理: {}",
            sql
        );
    }

    #[test]
    fn test_build_aggregate_sql_empty_field_rejected() {
        let result = DynamicTableManager::build_aggregate_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "",
            None,
        );
        assert!(result.is_err(), "空字段名应被拒绝");
    }

    // ===== build_create_table_sql (Generated Column) 测试 =====

    #[test]
    fn test_build_create_table_sql_with_generated_columns() {
        use crate::manifest::{PluginEntity, PluginField, PluginFieldType};

        let entity = PluginEntity {
            name: "customer".to_string(),
            display_name: "客户".to_string(),
            fields: vec![
                PluginField {
                    name: "code".to_string(),
                    field_type: PluginFieldType::String,
                    required: true,
                    unique: true,
                    display_name: Some("编码".to_string()),
                    searchable: Some(true),
                    ..PluginField::default_for_field()
                },
                PluginField {
                    name: "level".to_string(),
                    field_type: PluginFieldType::String,
                    filterable: Some(true),
                    display_name: Some("等级".to_string()),
                    ..PluginField::default_for_field()
                },
                PluginField {
                    name: "sort_order".to_string(),
                    field_type: PluginFieldType::Integer,
                    sortable: Some(true),
                    display_name: Some("排序".to_string()),
                    ..PluginField::default_for_field()
                },
            ],
            indexes: vec![],
            relations: vec![],
            data_scope: None,
            is_public: None,
            importable: None,
            exportable: None,
        };

        let sql = DynamicTableManager::build_create_table_sql("erp_crm", &entity);
        assert!(sql.contains("_f_code"), "应包含 _f_code Generated Column");
        assert!(sql.contains("_f_level"), "应包含 _f_level Generated Column");
        assert!(
            sql.contains("_f_sort_order"),
            "应包含 _f_sort_order Generated Column"
        );
        assert!(
            sql.contains("GENERATED ALWAYS AS"),
            "应包含 GENERATED ALWAYS AS"
        );
        assert!(sql.contains("::INTEGER"), "Integer 字段应有类型转换");
    }

    #[test]
    fn test_build_create_table_sql_pg_trgm_search_index() {
        use crate::manifest::{PluginEntity, PluginField, PluginFieldType};

        let entity = PluginEntity {
            name: "customer".to_string(),
            display_name: "客户".to_string(),
            fields: vec![PluginField {
                name: "name".to_string(),
                field_type: PluginFieldType::String,
                searchable: Some(true),
                display_name: Some("名称".to_string()),
                ..PluginField::default_for_field()
            }],
            indexes: vec![],
            relations: vec![],
            data_scope: None,
            is_public: None,
            importable: None,
            exportable: None,
        };

        let sql = DynamicTableManager::build_create_table_sql("erp_crm", &entity);
        assert!(
            sql.contains("gin_trgm_ops"),
            "searchable 字段应使用 pg_trgm GIN 索引"
        );
    }

    // ===== field_reference_fn + build_filtered_query_sql_ex 测试 =====

    #[test]
    fn test_field_reference_uses_generated_column() {
        let generated_fields = vec![
            "code".to_string(),
            "status".to_string(),
            "level".to_string(),
        ];
        let ref_fn = DynamicTableManager::field_reference_fn(&generated_fields);
        assert_eq!(ref_fn("code"), "\"_f_code\"");
        assert_eq!(ref_fn("status"), "\"_f_status\"");
        assert_eq!(ref_fn("name"), "\"data\"->>'name'");
        assert_eq!(ref_fn("remark"), "\"data\"->>'remark'");
    }

    #[test]
    fn test_filtered_query_uses_generated_column_for_sort() {
        let generated_fields = vec!["code".to_string(), "level".to_string()];
        let (sql, _) = DynamicTableManager::build_filtered_query_sql_ex(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            None,
            None,
            Some("level".to_string()),
            Some("asc".to_string()),
            &generated_fields,
        )
        .unwrap();
        assert!(
            sql.contains("ORDER BY \"_f_level\" ASC"),
            "排序应使用 Generated Column，got: {}",
            sql
        );
    }

    #[test]
    fn test_filtered_query_uses_generated_column_for_filter() {
        let generated_fields = vec!["status".to_string()];
        let (sql, _) = DynamicTableManager::build_filtered_query_sql_ex(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            0,
            Some(serde_json::json!({"status": "active"})),
            None,
            None,
            None,
            &generated_fields,
        )
        .unwrap();
        assert!(
            sql.contains("\"_f_status\" = $"),
            "过滤应使用 Generated Column，got: {}",
            sql
        );
    }

    // ===== Keyset Pagination 测试 =====

    #[test]
    fn test_keyset_cursor_encode_decode() {
        let cursor = DynamicTableManager::encode_cursor(
            &["测试值".to_string()],
            &Uuid::parse_str("00000000-0000-0000-0000-000000000042").unwrap(),
        );
        let decoded = DynamicTableManager::decode_cursor(&cursor).unwrap();
        assert_eq!(decoded.0, vec!["测试值"]);
        assert_eq!(
            decoded.1,
            Uuid::parse_str("00000000-0000-0000-0000-000000000042").unwrap()
        );
    }

    #[test]
    fn test_keyset_sql_first_page() {
        let (sql, _) = DynamicTableManager::build_keyset_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            None,
            Some("_f_name".to_string()),
            "ASC",
            &[],
        )
        .unwrap();
        assert!(sql.contains("ORDER BY"), "应有 ORDER BY");
        assert!(!sql.contains("ROW("), "第一页不应有 cursor 条件");
    }

    #[test]
    fn test_keyset_sql_with_cursor() {
        let cursor = DynamicTableManager::encode_cursor(
            &["Alice".to_string()],
            &Uuid::parse_str("00000000-0000-0000-0000-000000000100").unwrap(),
        );
        let (sql, values) = DynamicTableManager::build_keyset_query_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            20,
            Some(cursor),
            Some("_f_name".to_string()),
            "ASC",
            &[],
        )
        .unwrap();
        assert!(sql.contains("ROW("), "cursor 条件应使用 ROW 比较");
        assert!(
            values.len() >= 4,
            "应有 tenant_id + cursor_val + cursor_id + limit"
        );
    }

    // ===== build_data_scope_condition_with_params 测试 =====

    #[test]
    fn test_build_data_scope_condition_self() {
        let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
            "self",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &[],
            1,
            &[],
        );
        assert!(
            sql.contains("\"data\"->>'owner_id'"),
            "self 应包含 owner_id 条件, got: {}",
            sql
        );
        assert!(
            sql.contains("\"created_by\""),
            "self 应包含 created_by 条件, got: {}",
            sql
        );
        assert_eq!(values.len(), 2);
    }

    #[test]
    fn test_build_data_scope_condition_department() {
        let dept_members = vec![
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
        ];
        let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
            "department",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &dept_members,
            2,
            &[],
        );
        assert!(
            sql.contains("IN"),
            "department 应使用 IN 条件, got: {}",
            sql
        );
        assert!(sql.contains("$2"), "参数索引应从 2 开始, got: {}", sql);
        assert!(sql.contains("$3"), "第二个参数索引应为 3, got: {}", sql);
        assert_eq!(values.len(), 2);
    }

    #[test]
    fn test_build_data_scope_condition_department_empty_degrades_to_self() {
        let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
            "department",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &[],
            1,
            &[],
        );
        assert!(
            sql.contains("\"created_by\""),
            "空部门应退化为 self 范围, got: {}",
            sql
        );
        assert_eq!(values.len(), 2);
    }

    #[test]
    fn test_build_data_scope_condition_all() {
        let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
            "all",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &[],
            1,
            &[],
        );
        assert!(sql.is_empty(), "all 应返回空条件");
        assert!(values.is_empty(), "all 应返回空参数");
    }

    #[test]
    fn test_build_data_scope_condition_department_tree() {
        let dept_members = vec![
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
            Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
        ];
        let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
            "department_tree",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &dept_members,
            5,
            &[],
        );
        assert!(
            sql.contains("IN"),
            "department_tree 应使用 IN 条件, got: {}",
            sql
        );
        assert_eq!(values.len(), 3);
    }

    #[test]
    fn test_build_data_scope_condition_with_generated_column() {
        let generated = vec!["owner_id".to_string()];
        let (sql, _) = DynamicTableManager::build_data_scope_condition_with_params(
            "self",
            &Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "owner_id",
            &[],
            1,
            &generated,
        );
        assert!(
            sql.contains("\"_f_owner_id\""),
            "generated column 应使用 _f_ 前缀, got: {}",
            sql
        );
    }

    // ===== build_timeseries_sql 测试 =====

    #[test]
    fn test_build_timeseries_sql_day_grain() {
        let (sql, values) = DynamicTableManager::build_timeseries_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "occurred_at",
            "day",
            None,
            None,
        )
        .unwrap();
        assert!(sql.contains("date_trunc('day'"), "应有 day 粒度");
        assert!(sql.contains("GROUP BY"), "应有 GROUP BY");
        assert!(sql.contains("ORDER BY period"), "应按 period 排序");
        assert_eq!(values.len(), 1, "仅 tenant_id");
    }

    #[test]
    fn test_build_timeseries_sql_month_grain() {
        let (sql, _) = DynamicTableManager::build_timeseries_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "created_date",
            "month",
            None,
            None,
        )
        .unwrap();
        assert!(sql.contains("date_trunc('month'"), "应有 month 粒度");
    }

    #[test]
    fn test_build_timeseries_sql_with_date_range() {
        let (sql, values) = DynamicTableManager::build_timeseries_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "occurred_at",
            "week",
            Some("2026-01-01"),
            Some("2026-04-01"),
        )
        .unwrap();
        assert!(sql.contains("date_trunc('week'"), "应有 week 粒度");
        assert!(sql.contains(">="), "应有 start 条件");
        assert!(sql.contains("<"), "应有 end 条件");
        assert_eq!(values.len(), 3, "tenant_id + start + end");
    }

    #[test]
    fn test_build_timeseries_sql_invalid_grain() {
        let result = DynamicTableManager::build_timeseries_sql(
            "plugin_test",
            Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
            "occurred_at",
            "hour",
            None,
            None,
        );
        assert!(result.is_err(), "不支持的 grain 应报错");
    }

    // ===== sanitize_identifier SQL 注入防护测试 =====

    #[test]
    fn test_sanitize_removes_special_chars() {
        let result = sanitize_identifier("table;name'here\"with`special");
        assert!(!result.contains(';'), "分号应被替换: {}", result);
        assert!(!result.contains('\''), "单引号应被替换: {}", result);
        assert!(!result.contains('"'), "双引号应被替换: {}", result);
        assert!(!result.contains('`'), "反引号应被替换: {}", result);
    }

    #[test]
    fn test_sanitize_allows_alphanumeric_underscore() {
        let result = sanitize_identifier("my_table_123");
        assert_eq!(result, "my_table_123", "合法标识符应原样保留");
    }

    #[test]
    fn test_sanitize_handles_drop_table() {
        let result = sanitize_identifier("users; DROP TABLE users;");
        assert_eq!(
            result, "users__DROP_TABLE_users_",
            "DROP TABLE 注入应被清理为下划线: {}",
            result
        );
        assert!(!result.contains(';'), "不应包含分号: {}", result);
    }

    #[test]
    fn test_sanitize_handles_sql_comment() {
        let result = sanitize_identifier("users--");
        assert_eq!(result, "users__", "SQL 注释应被替换为下划线: {}", result);
        assert!(!result.contains('-'), "不应包含连字符: {}", result);
    }

    #[test]
    fn test_sanitize_handles_union_injection() {
        let result = sanitize_identifier("users UNION SELECT");
        assert_eq!(
            result, "users_UNION_SELECT",
            "UNION 注入中空格应被替换为下划线: {}",
            result
        );
        assert!(!result.contains(' '), "不应包含空格: {}", result);
    }

    #[test]
    fn test_sanitize_empty_string() {
        let result = sanitize_identifier("");
        assert_eq!(result, "", "空字符串应保持为空");
    }
}
