use std::collections::HashMap;

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

use crate::dto::{CreateOrganizationReq, OrganizationResp, UpdateOrganizationReq};
use crate::entity::department;
use crate::entity::organization;
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;

/// Organization CRUD service -- create, read, update, soft-delete organizations
/// within a tenant, supporting tree-structured hierarchy with path and level.
pub struct OrgService;

impl OrgService {
    /// Fetch all organizations for a tenant as a flat list (not deleted).
    pub async fn list_flat(
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<organization::Model>> {
        let items = organization::Entity::find()
            .filter(organization::Column::TenantId.eq(tenant_id))
            .filter(organization::Column::DeletedAt.is_null())
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        Ok(items)
    }

    /// Fetch all organizations for a tenant as a nested tree.
    ///
    /// Root nodes have `parent_id = None`. Children are grouped by `parent_id`.
    pub async fn get_tree(
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<OrganizationResp>> {
        let items = Self::list_flat(tenant_id, db).await?;
        Ok(build_org_tree(&items))
    }

    /// Create a new organization within the current tenant.
    ///
    /// If `parent_id` is provided, computes `path` from the parent's path and id,
    /// and sets `level = parent.level + 1`. Otherwise, level defaults to 1.
    pub async fn create(
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &CreateOrganizationReq,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<OrganizationResp> {
        // Check code uniqueness within tenant if code is provided
        if let Some(ref code) = req.code {
            let existing = organization::Entity::find()
                .filter(organization::Column::TenantId.eq(tenant_id))
                .filter(organization::Column::Code.eq(code.as_str()))
                .filter(organization::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 tenant
        let name_exists = organization::Entity::find()
            .filter(organization::Column::TenantId.eq(tenant_id))
            .filter(organization::Column::Name.eq(&req.name))
            .filter(organization::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()));
        }

        let (path, level) = if let Some(parent_id) = req.parent_id {
            let parent = organization::Entity::find_by_id(parent_id)
                .one(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?
                .filter(|p| p.tenant_id == tenant_id && p.deleted_at.is_none())
                .ok_or_else(|| AuthError::Validation("父级组织不存在".to_string()))?;

            let parent_path = parent.path.clone().unwrap_or_default();
            let computed_path = format!("{}{}/", parent_path, parent.id);
            (Some(computed_path), parent.level + 1)
        } else {
            (None, 1)
        };

        let now = Utc::now();
        let id = Uuid::now_v7();
        let model = organization::ActiveModel {
            id: Set(id),
            tenant_id: Set(tenant_id),
            name: Set(req.name.clone()),
            code: Set(req.code.clone()),
            parent_id: Set(req.parent_id),
            path: Set(path),
            level: Set(level),
            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(
                    "organization.created",
                    tenant_id,
                    serde_json::json!({ "org_id": id, "name": req.name }),
                ),
                db,
            )
            .await;

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

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

    /// Update editable organization fields (name, code, sort_order).
    pub async fn update(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &UpdateOrganizationReq,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<OrganizationResp> {
        let model = organization::Entity::find_by_id(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()))?;

        // If code is being changed, check uniqueness
        if let Some(ref new_code) = req.code
            && Some(new_code) != model.code.as_ref()
        {
            let existing = organization::Entity::find()
                .filter(organization::Column::TenantId.eq(tenant_id))
                .filter(organization::Column::Code.eq(new_code.as_str()))
                .filter(organization::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 (exclude self)
        if let Some(ref new_name) = req.name
            && new_name != &model.name
        {
            let name_exists = organization::Entity::find()
                .filter(organization::Column::TenantId.eq(tenant_id))
                .filter(organization::Column::Name.eq(new_name.as_str()))
                .filter(organization::Column::DeletedAt.is_null())
                .filter(organization::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: organization::ActiveModel = model.into();

        if let Some(ref name) = req.name {
            active.name = Set(name.clone());
        }
        if let Some(ref code) = req.code {
            active.code = Set(Some(code.clone()));
        }
        if let Some(sort_order) = req.sort_order {
            active.sort_order = Set(sort_order);
        }

        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),
                "organization.update",
                "organization",
            )
            .with_resource_id(id),
            db,
        )
        .await;

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

    /// Soft-delete an organization by setting the `deleted_at` timestamp.
    pub async fn delete(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<()> {
        let model = organization::Entity::find_by_id(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 for child organizations
        let children = organization::Entity::find()
            .filter(organization::Column::TenantId.eq(tenant_id))
            .filter(organization::Column::ParentId.eq(id))
            .filter(organization::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 departments under this organization
        let depts = department::Entity::find()
            .filter(department::Column::TenantId.eq(tenant_id))
            .filter(department::Column::OrgId.eq(id))
            .filter(department::Column::DeletedAt.is_null())
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;
        if depts.is_some() {
            return Err(AuthError::Validation(
                "该组织下存在部门，无法删除".to_string(),
            ));
        }

        let current_version = model.version;
        let mut active: organization::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(
                    "organization.deleted",
                    tenant_id,
                    serde_json::json!({ "org_id": id }),
                ),
                db,
            )
            .await;

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

        Ok(())
    }
}

/// Build a nested tree of `OrganizationResp` from a flat list of models.
///
/// Root nodes (parent_id = None) form the top level. Each node recursively
/// includes its children grouped by parent_id.
pub(crate) fn build_org_tree(items: &[organization::Model]) -> Vec<OrganizationResp> {
    let mut children_map: HashMap<Option<Uuid>, Vec<&organization::Model>> = HashMap::new();
    for item in items {
        children_map.entry(item.parent_id).or_default().push(item);
    }

    fn build_node(
        item: &organization::Model,
        map: &HashMap<Option<Uuid>, Vec<&organization::Model>>,
    ) -> OrganizationResp {
        let children = map
            .get(&Some(item.id))
            .map(|items| items.iter().map(|i| build_node(i, map)).collect())
            .unwrap_or_default();
        OrganizationResp {
            id: item.id,
            name: item.name.clone(),
            code: item.code.clone(),
            parent_id: item.parent_id,
            path: item.path.clone(),
            level: item.level,
            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()
}

#[cfg(test)]
mod tests {
    use chrono::Utc;
    use uuid::Uuid;

    use crate::entity::organization;

    use super::*;

    fn make_org(
        id: Uuid,
        tenant_id: Uuid,
        name: &str,
        parent_id: Option<Uuid>,
        level: i32,
        version: i32,
    ) -> organization::Model {
        organization::Model {
            id,
            tenant_id,
            name: name.to_string(),
            code: None,
            parent_id,
            path: None,
            level,
            sort_order: 0,
            created_at: Utc::now(),
            updated_at: Utc::now(),
            created_by: Uuid::now_v7(),
            updated_by: Uuid::now_v7(),
            deleted_at: None,
            version,
        }
    }

    #[test]
    fn build_org_tree_empty() {
        let tree = build_org_tree(&[]);
        assert!(tree.is_empty());
    }

    #[test]
    fn build_org_tree_single_root() {
        let tid = Uuid::now_v7();
        let root_id = Uuid::now_v7();
        let items = vec![make_org(root_id, tid, "总公司", None, 1, 1)];

        let tree = build_org_tree(&items);
        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0].name, "总公司");
        assert!(tree[0].children.is_empty());
    }

    #[test]
    fn build_org_tree_multiple_roots() {
        let tid = Uuid::now_v7();
        let items = vec![
            make_org(Uuid::now_v7(), tid, "公司A", None, 1, 1),
            make_org(Uuid::now_v7(), tid, "公司B", None, 1, 1),
        ];

        let tree = build_org_tree(&items);
        assert_eq!(tree.len(), 2);
    }

    #[test]
    fn build_org_tree_nested_children() {
        let tid = Uuid::now_v7();
        let root_id = Uuid::now_v7();
        let child1_id = Uuid::now_v7();
        let child2_id = Uuid::now_v7();
        let grandchild_id = Uuid::now_v7();

        let items = vec![
            make_org(root_id, tid, "总公司", None, 1, 1),
            make_org(child1_id, tid, "分公司A", Some(root_id), 2, 1),
            make_org(child2_id, tid, "分公司B", Some(root_id), 2, 1),
            make_org(grandchild_id, tid, "部门A1", Some(child1_id), 3, 1),
        ];

        let tree = build_org_tree(&items);
        assert_eq!(tree.len(), 1); // one root
        assert_eq!(tree[0].children.len(), 2); // two children
        assert_eq!(tree[0].children[0].children.len(), 1); // one grandchild
        assert_eq!(tree[0].children[0].children[0].name, "部门A1");
    }

    #[test]
    fn build_org_tree_deep_nesting() {
        let tid = Uuid::now_v7();
        let l1 = Uuid::now_v7();
        let l2 = Uuid::now_v7();
        let l3 = Uuid::now_v7();
        let l4 = Uuid::now_v7();

        let items = vec![
            make_org(l1, tid, "L1", None, 1, 1),
            make_org(l2, tid, "L2", Some(l1), 2, 1),
            make_org(l3, tid, "L3", Some(l2), 3, 1),
            make_org(l4, tid, "L4", Some(l3), 4, 1),
        ];

        let tree = build_org_tree(&items);
        assert_eq!(tree.len(), 1);
        assert_eq!(tree[0].children[0].children[0].children[0].name, "L4");
    }

    #[test]
    fn build_org_tree_preserves_version() {
        let tid = Uuid::now_v7();
        let root_id = Uuid::now_v7();
        let items = vec![make_org(root_id, tid, "测试", None, 1, 5)];

        let tree = build_org_tree(&items);
        assert_eq!(tree[0].version, 5);
    }
}
