use std::collections::HashMap;

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

use crate::dto::{CreateDepartmentReq, DepartmentResp, UpdateDepartmentReq};
use crate::entity::department;
use crate::entity::organization;
use crate::entity::position;
use crate::entity::user_department;
use crate::error::{AuthError, AuthResult};
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::error::check_version;
use erp_core::events::EventBus;

/// Department CRUD service -- create, read, update, soft-delete departments
/// within an organization, supporting tree-structured hierarchy.
pub struct DeptService;

impl DeptService {
    /// Fetch all departments for an organization as a nested tree.
    ///
    /// Root departments (parent_id = None) form the top level.
    pub async fn list_tree(
        org_id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<DepartmentResp>> {
        // Verify the organization exists
        let _org = organization::Entity::find_by_id(org_id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|o| o.tenant_id == tenant_id && o.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("组织不存在".to_string()))?;

        let items = department::Entity::find()
            .filter(department::Column::TenantId.eq(tenant_id))
            .filter(department::Column::OrgId.eq(org_id))
            .filter(department::Column::DeletedAt.is_null())
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        Ok(build_dept_tree(&items))
    }

    /// Create a new department under the specified organization.
    ///
    /// If `parent_id` is provided, computes `path` from the parent department.
    /// Otherwise, path is computed from the organization root.
    pub async fn create(
        org_id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &CreateDepartmentReq,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<DepartmentResp> {
        // Verify the organization exists
        let org = organization::Entity::find_by_id(org_id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|o| o.tenant_id == tenant_id && o.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("组织不存在".to_string()))?;

        // Check code uniqueness within tenant if code is provided
        if let Some(ref code) = req.code {
            let existing = department::Entity::find()
                .filter(department::Column::TenantId.eq(tenant_id))
                .filter(department::Column::Code.eq(code.as_str()))
                .filter(department::Column::DeletedAt.is_null())
                .one(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?;
            if existing.is_some() {
                return Err(AuthError::Validation("部门编码已存在".to_string()));
            }
        }

        // Check name uniqueness within the same organization
        let name_exists = department::Entity::find()
            .filter(department::Column::TenantId.eq(tenant_id))
            .filter(department::Column::OrgId.eq(org_id))
            .filter(department::Column::Name.eq(&req.name))
            .filter(department::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;
        if name_exists.is_some() {
            return Err(AuthError::Validation("部门名称已存在".to_string()));
        }

        // Compute path from parent department or organization root
        let path = if let Some(parent_id) = req.parent_id {
            let parent = department::Entity::find_by_id(parent_id)
                .one(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?
                .filter(|d| {
                    d.tenant_id == tenant_id && d.org_id == org_id && d.deleted_at.is_none()
                })
                .ok_or_else(|| AuthError::Validation("父级部门不存在".to_string()))?;

            let parent_path = parent.path.clone().unwrap_or_default();
            Some(format!("{}{}/", parent_path, parent.id))
        } else {
            // Root department under the organization
            let org_path = org.path.clone().unwrap_or_default();
            Some(format!("{}{}/", org_path, org.id))
        };

        let now = Utc::now();
        let id = Uuid::now_v7();
        let model = department::ActiveModel {
            id: Set(id),
            tenant_id: Set(tenant_id),
            org_id: Set(org_id),
            name: Set(req.name.clone()),
            code: Set(req.code.clone()),
            parent_id: Set(req.parent_id),
            manager_id: Set(req.manager_id),
            path: Set(path),
            sort_order: Set(req.sort_order.unwrap_or(0)),
            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| AuthError::Validation(e.to_string()))?;

        event_bus
            .publish(
                erp_core::events::DomainEvent::new(
                    "department.created",
                    tenant_id,
                    serde_json::json!({ "dept_id": id, "org_id": org_id, "name": req.name }),
                ),
                db,
            )
            .await;

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

        Ok(DepartmentResp {
            id,
            org_id,
            name: req.name.clone(),
            code: req.code.clone(),
            parent_id: req.parent_id,
            manager_id: req.manager_id,
            path: None,
            sort_order: req.sort_order.unwrap_or(0),
            children: vec![],
            version: 1,
        })
    }

    /// Update editable department fields (name, code, manager_id, sort_order).
    pub async fn update(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &UpdateDepartmentReq,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<DepartmentResp> {
        let model = department::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|d| d.tenant_id == tenant_id && d.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("部门不存在".to_string()))?;

        // If code is being changed, check uniqueness
        if let Some(new_code) = &req.code
            && Some(new_code) != model.code.as_ref()
        {
            let existing = department::Entity::find()
                .filter(department::Column::TenantId.eq(tenant_id))
                .filter(department::Column::Code.eq(new_code.as_str()))
                .filter(department::Column::DeletedAt.is_null())
                .one(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?;
            if existing.is_some() {
                return Err(AuthError::Validation("部门编码已存在".to_string()));
            }
        }

        // If name is being changed, check uniqueness within the same org (exclude self)
        if let Some(ref new_name) = req.name
            && new_name != &model.name
        {
            let name_exists = department::Entity::find()
                .filter(department::Column::TenantId.eq(tenant_id))
                .filter(department::Column::OrgId.eq(model.org_id))
                .filter(department::Column::Name.eq(new_name.as_str()))
                .filter(department::Column::DeletedAt.is_null())
                .filter(department::Column::Id.ne(id))
                .one(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?;
            if name_exists.is_some() {
                return Err(AuthError::Validation("部门名称已存在".to_string()));
            }
        }

        let next_ver = check_version(req.version, model.version)
            .map_err(|e| AuthError::Validation(e.to_string()))?;

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

        if let Some(n) = &req.name {
            active.name = Set(n.clone());
        }
        if let Some(c) = &req.code {
            active.code = Set(Some(c.clone()));
        }
        if let Some(mgr_id) = &req.manager_id {
            active.manager_id = Set(Some(*mgr_id));
        }
        if let Some(so) = &req.sort_order {
            active.sort_order = Set(*so);
        }

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

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

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

        Ok(DepartmentResp {
            id: updated.id,
            org_id: updated.org_id,
            name: updated.name.clone(),
            code: updated.code.clone(),
            parent_id: updated.parent_id,
            manager_id: updated.manager_id,
            path: updated.path.clone(),
            sort_order: updated.sort_order,
            children: vec![],
            version: updated.version,
        })
    }

    /// Soft-delete a department by setting the `deleted_at` timestamp.
    ///
    /// Will not delete if child departments exist.
    pub async fn delete(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<()> {
        let model = department::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|d| d.tenant_id == tenant_id && d.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("部门不存在".to_string()))?;

        // Check for child departments
        let children = department::Entity::find()
            .filter(department::Column::TenantId.eq(tenant_id))
            .filter(department::Column::ParentId.eq(id))
            .filter(department::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;
        if children.is_some() {
            return Err(AuthError::Validation(
                "该部门下存在子部门，无法删除".to_string(),
            ));
        }

        // Check for positions under this department
        let positions = position::Entity::find()
            .filter(position::Column::TenantId.eq(tenant_id))
            .filter(position::Column::DeptId.eq(id))
            .filter(position::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;
        if positions.is_some() {
            return Err(AuthError::Validation(
                "该部门下存在岗位，无法删除".to_string(),
            ));
        }

        // Check for users assigned to this department
        let users = user_department::Entity::find()
            .filter(user_department::Column::TenantId.eq(tenant_id))
            .filter(user_department::Column::DepartmentId.eq(id))
            .filter(user_department::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;
        if users.is_some() {
            return Err(AuthError::Validation(
                "该部门下存在用户，无法删除".to_string(),
            ));
        }

        let current_version = model.version;
        let mut active: department::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(current_version + 1);
        active
            .update(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

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

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

        Ok(())
    }
}

/// Build a nested tree of `DepartmentResp` from a flat list of models.
fn build_dept_tree(items: &[department::Model]) -> Vec<DepartmentResp> {
    let mut children_map: HashMap<Option<Uuid>, Vec<&department::Model>> = HashMap::new();
    for item in items {
        children_map.entry(item.parent_id).or_default().push(item);
    }

    fn build_node(
        item: &department::Model,
        map: &HashMap<Option<Uuid>, Vec<&department::Model>>,
    ) -> DepartmentResp {
        let children = map
            .get(&Some(item.id))
            .map(|items| items.iter().map(|i| build_node(i, map)).collect())
            .unwrap_or_default();
        DepartmentResp {
            id: item.id,
            org_id: item.org_id,
            name: item.name.clone(),
            code: item.code.clone(),
            parent_id: item.parent_id,
            manager_id: item.manager_id,
            path: item.path.clone(),
            sort_order: item.sort_order,
            children,
            version: item.version,
        }
    }

    children_map
        .get(&None)
        .map(|root_items| {
            root_items
                .iter()
                .map(|item| build_node(item, &children_map))
                .collect()
        })
        .unwrap_or_default()
}
