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

use crate::dto::{CreatePositionReq, PositionResp, UpdatePositionReq};
use crate::entity::department;
use crate::entity::position;
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;

/// Position CRUD service -- create, read, update, soft-delete positions
/// within a department.
pub struct PositionService;

impl PositionService {
    /// List all positions for a department within the given tenant.
    pub async fn list(
        dept_id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<PositionResp>> {
        // Verify the department exists
        let _dept = department::Entity::find_by_id(dept_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()))?;

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

        Ok(items
            .iter()
            .map(|p| PositionResp {
                id: p.id,
                dept_id: p.dept_id,
                name: p.name.clone(),
                code: p.code.clone(),
                level: p.level,
                sort_order: p.sort_order,
                version: p.version,
            })
            .collect())
    }

    /// Create a new position under the specified department.
    pub async fn create(
        dept_id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        req: &CreatePositionReq,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<PositionResp> {
        // Verify the department exists
        let _dept = department::Entity::find_by_id(dept_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 code uniqueness within tenant if code is provided
        if let Some(ref code) = req.code {
            let existing = position::Entity::find()
                .filter(position::Column::TenantId.eq(tenant_id))
                .filter(position::Column::Code.eq(code.as_str()))
                .filter(position::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()));
            }
        }

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

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

        Ok(PositionResp {
            id,
            dept_id,
            name: req.name.clone(),
            code: req.code.clone(),
            level: req.level.unwrap_or(1),
            sort_order: req.sort_order.unwrap_or(0),
            version: 1,
        })
    }

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

        // If code is being changed, check uniqueness
        if let Some(new_code) = &req.code
            && Some(new_code) != model.code.as_ref()
        {
            let existing = position::Entity::find()
                .filter(position::Column::TenantId.eq(tenant_id))
                .filter(position::Column::Code.eq(new_code.as_str()))
                .filter(position::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()));
            }
        }

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

        let mut active: position::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(l) = &req.level {
            active.level = Set(*l);
        }
        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), "position.update", "position")
                .with_resource_id(id),
            db,
        )
        .await;

        Ok(PositionResp {
            id: updated.id,
            dept_id: updated.dept_id,
            name: updated.name.clone(),
            code: updated.code.clone(),
            level: updated.level,
            sort_order: updated.sort_order,
            version: updated.version,
        })
    }

    /// Soft-delete a position 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 = position::Entity::find_by_id(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 current_version = model.version;
        let mut active: position::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(
                    "position.deleted",
                    tenant_id,
                    serde_json::json!({ "position_id": id }),
                ),
                db,
            )
            .await;

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

        Ok(())
    }
}
