use std::collections::HashMap;

use chrono::Utc;
use sea_orm::{
    ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder, Set,
};
use uuid::Uuid;

use crate::dto::{CreateMenuReq, MenuResp};
use crate::entity::{menu, menu_role};
use crate::error::{ConfigError, ConfigResult};
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::error::check_version;
use erp_core::events::EventBus;

/// 菜单 CRUD 服务 -- 创建、查询（树形/平铺）、更新、软删除菜单，
/// 以及管理菜单-角色关联。
pub struct MenuService;

impl MenuService {
    /// 通过角色 code 列表查找对应的角色 ID 列表。
    async fn resolve_role_ids(
        tenant_id: Uuid,
        role_codes: &[String],
        db: &sea_orm::DatabaseConnection,
    ) -> ConfigResult<Vec<Uuid>> {
        if role_codes.is_empty() {
            return Ok(vec![]);
        }
        let codes_csv: String = role_codes
            .iter()
            .map(|c| format!("'{}'", c.replace('\'', "''")))
            .collect::<Vec<_>>()
            .join(",");
        let sql = format!(
            "SELECT id FROM roles WHERE tenant_id = '{}' AND code IN ({}) AND deleted_at IS NULL",
            tenant_id, codes_csv
        );
        let stmt = sea_orm::Statement::from_string(sea_orm::DatabaseBackend::Postgres, sql);
        let rows = db.query_all(stmt).await?;

        Ok(rows
            .into_iter()
            .filter_map(|row| {
                let id: Uuid = row.try_get_by_index(0).ok()?;
                Some(id)
            })
            .collect())
    }

    pub async fn get_menu_tree(
        tenant_id: Uuid,
        role_codes: &[String],
        db: &sea_orm::DatabaseConnection,
    ) -> ConfigResult<Vec<MenuResp>> {
        // 0. admin 角色直接返回全部菜单，跳过 menu_roles 过滤
        if role_codes.iter().any(|c| c == "admin") {
            let all_menus = menu::Entity::find()
                .filter(menu::Column::TenantId.eq(tenant_id))
                .filter(menu::Column::DeletedAt.is_null())
                .order_by_asc(menu::Column::SortOrder)
                .all(db)
                .await
                .map_err(|e| ConfigError::Validation(e.to_string()))?;

            let mut children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
            for m in &all_menus {
                children_map.entry(m.parent_id).or_default().push(m);
            }
            let roots = children_map.get(&None).cloned().unwrap_or_default();
            return Ok(Self::build_tree(&roots, &children_map));
        }

        // 1. 将角色 code 转换为 UUID
        let role_ids = Self::resolve_role_ids(tenant_id, role_codes, db).await?;

        // 2. 查询租户下所有未删除的菜单，按 sort_order 排序
        let all_menus = menu::Entity::find()
            .filter(menu::Column::TenantId.eq(tenant_id))
            .filter(menu::Column::DeletedAt.is_null())
            .order_by_asc(menu::Column::SortOrder)
            .all(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        // 3. 通过 menu_roles 表过滤
        let visible_menu_ids: Option<Vec<Uuid>> = if !role_ids.is_empty() {
            let mr_rows = menu_role::Entity::find()
                .filter(menu_role::Column::TenantId.eq(tenant_id))
                .filter(menu_role::Column::RoleId.is_in(role_ids.iter().copied()))
                .filter(menu_role::Column::DeletedAt.is_null())
                .all(db)
                .await
                .map_err(|e| ConfigError::Validation(e.to_string()))?;

            let ids: Vec<Uuid> = mr_rows.iter().map(|mr| mr.menu_id).collect();
            if ids.is_empty() {
                Some(vec![]) // 无菜单关联 = 不显示
            } else {
                Some(ids)
            }
        } else {
            Some(vec![]) // 无角色 = 不显示任何菜单
        };

        // 3. 按 parent_id 分组构建 HashMap
        let filtered: Vec<&menu::Model> = match &visible_menu_ids {
            Some(ids) => all_menus.iter().filter(|m| ids.contains(&m.id)).collect(),
            None => all_menus.iter().collect(),
        };

        let mut children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        for m in &filtered {
            children_map.entry(m.parent_id).or_default().push(*m);
        }

        // 4. 递归构建树形结构（从 parent_id == None 的根节点开始）
        let roots = children_map.get(&None).cloned().unwrap_or_default();
        let tree = Self::build_tree(&roots, &children_map);

        Ok(tree)
    }

    /// 获取当前租户下所有菜单的平铺列表（无角色过滤）。
    pub async fn get_flat_list(
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> ConfigResult<Vec<MenuResp>> {
        let menus = menu::Entity::find()
            .filter(menu::Column::TenantId.eq(tenant_id))
            .filter(menu::Column::DeletedAt.is_null())
            .order_by_asc(menu::Column::SortOrder)
            .all(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        Ok(menus
            .iter()
            .map(|m| MenuResp {
                id: m.id,
                parent_id: m.parent_id,
                title: m.title.clone(),
                path: m.path.clone(),
                icon: m.icon.clone(),
                sort_order: m.sort_order,
                visible: m.visible,
                menu_type: m.menu_type.clone(),
                permission: m.permission.clone(),
                children: vec![],
                version: m.version,
            })
            .collect())
    }

    /// 创建菜单并可选地关联角色。
    pub async fn create(
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &CreateMenuReq,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> ConfigResult<MenuResp> {
        let now = Utc::now();
        let id = Uuid::now_v7();

        let model = menu::ActiveModel {
            id: Set(id),
            tenant_id: Set(tenant_id),
            parent_id: Set(req.parent_id),
            title: Set(req.title.clone()),
            path: Set(req.path.clone()),
            icon: Set(req.icon.clone()),
            sort_order: Set(req.sort_order.unwrap_or(0)),
            visible: Set(req.visible.unwrap_or(true)),
            menu_type: Set(req.menu_type.clone().unwrap_or_else(|| "menu".to_string())),
            permission: Set(req.permission.clone()),
            created_at: Set(now),
            updated_at: Set(now),
            created_by: Set(operator_id),
            updated_by: Set(operator_id),
            deleted_at: Set(None),
            version: Set(1),
        };
        model
            .insert(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        // 关联角色（如果提供了 role_ids）
        if let Some(role_ids) = &req.role_ids
            && !role_ids.is_empty()
        {
            Self::assign_roles(id, role_ids, tenant_id, operator_id, db).await?;
        }

        event_bus
            .publish(
                erp_core::events::DomainEvent::new(
                    "menu.created",
                    tenant_id,
                    serde_json::json!({ "menu_id": id, "title": req.title }),
                ),
                db,
            )
            .await;

        audit_service::record(
            AuditLog::new(tenant_id, Some(operator_id), "menu.create", "menu").with_resource_id(id),
            db,
        )
        .await;

        Ok(MenuResp {
            id,
            parent_id: req.parent_id,
            title: req.title.clone(),
            path: req.path.clone(),
            icon: req.icon.clone(),
            sort_order: req.sort_order.unwrap_or(0),
            visible: req.visible.unwrap_or(true),
            menu_type: req.menu_type.clone().unwrap_or_else(|| "menu".to_string()),
            permission: req.permission.clone(),
            children: vec![],
            version: 1,
        })
    }

    /// 更新菜单字段，并可选地重新关联角色。
    /// 使用乐观锁校验版本。
    pub async fn update(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &crate::dto::UpdateMenuReq,
        db: &sea_orm::DatabaseConnection,
    ) -> ConfigResult<MenuResp> {
        let model = menu::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?
            .filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
            .ok_or_else(|| ConfigError::NotFound(format!("菜单不存在: {id}")))?;

        let next_version =
            check_version(req.version, model.version).map_err(|_| ConfigError::VersionMismatch)?;

        let mut active: menu::ActiveModel = model.into();

        if let Some(title) = &req.title {
            active.title = Set(title.clone());
        }
        if let Some(path) = &req.path {
            active.path = Set(Some(path.clone()));
        }
        if let Some(icon) = &req.icon {
            active.icon = Set(Some(icon.clone()));
        }
        if let Some(sort_order) = req.sort_order {
            active.sort_order = Set(sort_order);
        }
        if let Some(visible) = req.visible {
            active.visible = Set(visible);
        }
        if let Some(permission) = &req.permission {
            active.permission = Set(Some(permission.clone()));
        }

        active.updated_at = Set(Utc::now());
        active.updated_by = Set(operator_id);
        active.version = Set(next_version);

        let updated = active
            .update(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        // 如果提供了 role_ids，重新关联角色
        if let Some(role_ids) = &req.role_ids {
            Self::assign_roles(id, role_ids, tenant_id, operator_id, db).await?;
        }

        audit_service::record(
            AuditLog::new(tenant_id, Some(operator_id), "menu.update", "menu").with_resource_id(id),
            db,
        )
        .await;

        Ok(MenuResp {
            id: updated.id,
            parent_id: updated.parent_id,
            title: updated.title.clone(),
            path: updated.path.clone(),
            icon: updated.icon.clone(),
            sort_order: updated.sort_order,
            visible: updated.visible,
            menu_type: updated.menu_type.clone(),
            permission: updated.permission.clone(),
            children: vec![],
            version: updated.version,
        })
    }

    /// 软删除菜单。使用乐观锁校验版本。
    pub async fn delete(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        version: i32,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> ConfigResult<()> {
        let model = menu::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?
            .filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
            .ok_or_else(|| ConfigError::NotFound(format!("菜单不存在: {id}")))?;

        let next_version =
            check_version(version, model.version).map_err(|_| ConfigError::VersionMismatch)?;

        let mut active: menu::ActiveModel = model.into();
        active.deleted_at = Set(Some(Utc::now()));
        active.updated_at = Set(Utc::now());
        active.updated_by = Set(operator_id);
        active.version = Set(next_version);
        active
            .update(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        event_bus
            .publish(
                erp_core::events::DomainEvent::new(
                    "menu.deleted",
                    tenant_id,
                    serde_json::json!({ "menu_id": id }),
                ),
                db,
            )
            .await;

        audit_service::record(
            AuditLog::new(tenant_id, Some(operator_id), "menu.delete", "menu").with_resource_id(id),
            db,
        )
        .await;

        Ok(())
    }

    /// 替换菜单的角色关联。
    ///
    /// 软删除现有关联行，然后插入新关联（参考 RoleService::assign_permissions 模式）。
    pub async fn assign_roles(
        menu_id: Uuid,
        role_ids: &[Uuid],
        tenant_id: Uuid,
        operator_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> ConfigResult<()> {
        // 验证菜单存在且属于当前租户
        let _menu = menu::Entity::find_by_id(menu_id)
            .one(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?
            .filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
            .ok_or_else(|| ConfigError::NotFound(format!("菜单不存在: {menu_id}")))?;

        // 软删除现有关联
        let existing = menu_role::Entity::find()
            .filter(menu_role::Column::MenuId.eq(menu_id))
            .filter(menu_role::Column::TenantId.eq(tenant_id))
            .filter(menu_role::Column::DeletedAt.is_null())
            .all(db)
            .await
            .map_err(|e| ConfigError::Validation(e.to_string()))?;

        let now = Utc::now();
        for mr in existing {
            let mut active: menu_role::ActiveModel = mr.into();
            active.deleted_at = Set(Some(now));
            active.updated_at = Set(now);
            active.updated_by = Set(operator_id);
            active.version = Set(active.version.take().unwrap_or(0) + 1);
            active
                .update(db)
                .await
                .map_err(|e| ConfigError::Validation(e.to_string()))?;
        }

        // 插入新关联
        for role_id in role_ids {
            let mr = menu_role::ActiveModel {
                id: Set(Uuid::now_v7()),
                menu_id: Set(menu_id),
                role_id: Set(*role_id),
                tenant_id: Set(tenant_id),
                created_at: Set(now),
                updated_at: Set(now),
                created_by: Set(operator_id),
                updated_by: Set(operator_id),
                deleted_at: Set(None),
                version: Set(1),
            };
            mr.insert(db)
                .await
                .map_err(|e| ConfigError::Validation(e.to_string()))?;
        }

        Ok(())
    }

    /// 递归构建菜单树。
    fn build_tree<'a>(
        nodes: &[&'a menu::Model],
        children_map: &HashMap<Option<Uuid>, Vec<&'a menu::Model>>,
    ) -> Vec<MenuResp> {
        nodes
            .iter()
            .map(|m| {
                let children = children_map.get(&Some(m.id)).cloned().unwrap_or_default();
                MenuResp {
                    id: m.id,
                    parent_id: m.parent_id,
                    title: m.title.clone(),
                    path: m.path.clone(),
                    icon: m.icon.clone(),
                    sort_order: m.sort_order,
                    visible: m.visible,
                    menu_type: m.menu_type.clone(),
                    permission: m.permission.clone(),
                    children: Self::build_tree(&children, children_map),
                    version: m.version,
                }
            })
            .collect()
    }
}

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

    /// 辅助：构造 menu::Model
    fn make_menu(id: Uuid, parent_id: Option<Uuid>, title: &str, sort_order: i32) -> menu::Model {
        let now = Utc::now();
        let tenant_id = Uuid::now_v7();
        menu::Model {
            id,
            tenant_id,
            parent_id,
            title: title.to_string(),
            path: Some(format!("/{}", title.to_lowercase())),
            icon: None,
            sort_order,
            visible: true,
            menu_type: "menu".to_string(),
            permission: None,
            created_at: now,
            updated_at: now,
            created_by: tenant_id,
            updated_by: tenant_id,
            deleted_at: None,
            version: 1,
        }
    }

    #[test]
    fn build_tree_empty_input() {
        let nodes: Vec<&menu::Model> = vec![];
        let children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        let tree = MenuService::build_tree(&nodes, &children_map);
        assert!(tree.is_empty());
    }

    #[test]
    fn build_tree_single_root() {
        let root_id = Uuid::now_v7();
        let root = make_menu(root_id, None, "首页", 0);

        let children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        let roots: Vec<&menu::Model> = vec![&root];
        let tree = MenuService::build_tree(&roots, &children_map);

        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0].id, root_id);
        assert_eq!(tree[0].title, "首页");
        assert!(tree[0].children.is_empty());
    }

    #[test]
    fn build_tree_two_levels() {
        // 根节点 -> 子节点1, 子节点2
        let root_id = Uuid::now_v7();
        let child1_id = Uuid::now_v7();
        let child2_id = Uuid::now_v7();

        let root = make_menu(root_id, None, "系统管理", 0);
        let child1 = make_menu(child1_id, Some(root_id), "用户管理", 1);
        let child2 = make_menu(child2_id, Some(root_id), "角色管理", 2);

        let mut children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        children_map.insert(Some(root_id), vec![&child1, &child2]);

        let roots: Vec<&menu::Model> = vec![&root];
        let tree = MenuService::build_tree(&roots, &children_map);

        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0].children.len(), 2);
        assert_eq!(tree[0].children[0].title, "用户管理");
        assert_eq!(tree[0].children[1].title, "角色管理");
    }

    #[test]
    fn build_tree_three_levels() {
        // 根 -> 子 -> 孙
        let root_id = Uuid::now_v7();
        let child_id = Uuid::now_v7();
        let grandchild_id = Uuid::now_v7();

        let root = make_menu(root_id, None, "系统管理", 0);
        let child = make_menu(child_id, Some(root_id), "用户管理", 1);
        let grandchild = make_menu(grandchild_id, Some(child_id), "用户详情", 0);

        let mut children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        children_map.insert(Some(root_id), vec![&child]);
        children_map.insert(Some(child_id), vec![&grandchild]);

        let roots: Vec<&menu::Model> = vec![&root];
        let tree = MenuService::build_tree(&roots, &children_map);

        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0].children.len(), 1);
        assert_eq!(tree[0].children[0].children.len(), 1);
        assert_eq!(tree[0].children[0].children[0].title, "用户详情");
    }

    #[test]
    fn build_tree_multiple_roots() {
        // 两个独立的根节点
        let root1_id = Uuid::now_v7();
        let root2_id = Uuid::now_v7();

        let root1 = make_menu(root1_id, None, "首页", 0);
        let root2 = make_menu(root2_id, None, "系统管理", 1);

        let children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        let roots: Vec<&menu::Model> = vec![&root1, &root2];
        let tree = MenuService::build_tree(&roots, &children_map);

        assert_eq!(tree.len(), 2);
        assert_eq!(tree[0].title, "首页");
        assert_eq!(tree[1].title, "系统管理");
    }

    #[test]
    fn build_tree_preserves_model_fields() {
        let root_id = Uuid::now_v7();
        let now = Utc::now();
        let tenant_id = Uuid::now_v7();

        let root = menu::Model {
            id: root_id,
            tenant_id,
            parent_id: None,
            title: "设置".to_string(),
            path: Some("/settings".to_string()),
            icon: Some("SettingOutlined".to_string()),
            sort_order: 5,
            visible: false,
            menu_type: "directory".to_string(),
            permission: Some("settings:view".to_string()),
            created_at: now,
            updated_at: now,
            created_by: tenant_id,
            updated_by: tenant_id,
            deleted_at: None,
            version: 3,
        };

        let children_map: HashMap<Option<Uuid>, Vec<&menu::Model>> = HashMap::new();
        let roots: Vec<&menu::Model> = vec![&root];
        let tree = MenuService::build_tree(&roots, &children_map);

        assert_eq!(tree.len(), 1);
        let node = &tree[0];
        assert_eq!(node.id, root_id);
        assert_eq!(node.title, "设置");
        assert_eq!(node.path, Some("/settings".to_string()));
        assert_eq!(node.icon, Some("SettingOutlined".to_string()));
        assert_eq!(node.sort_order, 5);
        assert!(!node.visible);
        assert_eq!(node.menu_type, "directory");
        assert_eq!(node.permission, Some("settings:view".to_string()));
        assert_eq!(node.version, 3);
    }
}
