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

use crate::dto::{PermissionResp, RoleResp};
use crate::entity::{permission, role, role_permission};
use crate::error::AuthError;
use crate::error::AuthResult;
use erp_core::audit::AuditLog;
use erp_core::audit_service;
use erp_core::error::check_version;
use erp_core::events::EventBus;
use erp_core::types::Pagination;

/// Role CRUD service — create, read, update, soft-delete roles within a tenant,
/// and manage role-permission assignments.
pub struct RoleService;

impl RoleService {
    /// List roles within a tenant with pagination.
    ///
    /// Returns `(roles, total_count)`.
    pub async fn list(
        tenant_id: Uuid,
        pagination: &Pagination,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<(Vec<RoleResp>, u64)> {
        let paginator = role::Entity::find()
            .filter(role::Column::TenantId.eq(tenant_id))
            .filter(role::Column::DeletedAt.is_null())
            .paginate(db, pagination.limit());

        let total = paginator
            .num_items()
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        let page_index = pagination.page.unwrap_or(1).saturating_sub(1);
        let models = paginator
            .fetch_page(page_index)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        let resps: Vec<RoleResp> = models
            .iter()
            .map(|m| RoleResp {
                id: m.id,
                name: m.name.clone(),
                code: m.code.clone(),
                description: m.description.clone(),
                is_system: m.is_system,
                version: m.version,
            })
            .collect();

        Ok((resps, total))
    }

    /// Fetch a single role by ID, scoped to the given tenant.
    pub async fn get_by_id(
        id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<RoleResp> {
        let model = role::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|r| r.tenant_id == tenant_id && r.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("角色不存在".to_string()))?;

        Ok(RoleResp {
            id: model.id,
            name: model.name.clone(),
            code: model.code.clone(),
            description: model.description.clone(),
            is_system: model.is_system,
            version: model.version,
        })
    }

    /// Create a new role within the current tenant.
    ///
    /// Validates code uniqueness, then inserts the record and publishes
    /// a `role.created` domain event.
    pub async fn create(
        tenant_id: Uuid,
        operator_id: Uuid,
        name: &str,
        code: &str,
        description: &Option<String>,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<RoleResp> {
        // Check code uniqueness within tenant
        let existing = role::Entity::find()
            .filter(role::Column::TenantId.eq(tenant_id))
            .filter(role::Column::Code.eq(code))
            .filter(role::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 = role::ActiveModel {
            id: Set(id),
            tenant_id: Set(tenant_id),
            name: Set(name.to_string()),
            code: Set(code.to_string()),
            description: Set(description.clone()),
            is_system: Set(false),
            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(
                    "role.created",
                    tenant_id,
                    serde_json::json!({ "role_id": id, "code": code }),
                ),
                db,
            )
            .await;

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

        Ok(RoleResp {
            id,
            name: name.to_string(),
            code: code.to_string(),
            description: description.clone(),
            is_system: false,
            version: 1,
        })
    }

    /// Update editable role fields (name and description).
    ///
    /// Code and is_system cannot be changed after creation.
    pub async fn update(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        name: &Option<String>,
        description: &Option<String>,
        version: i32,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<RoleResp> {
        let model = role::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|r| r.tenant_id == tenant_id && r.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("角色不存在".to_string()))?;

        let old_json = serde_json::to_value(&model).unwrap_or(serde_json::Value::Null);

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

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

        if let Some(name) = name {
            active.name = Set(name.clone());
        }
        if let Some(desc) = description {
            active.description = Set(Some(desc.clone()));
        }

        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()))?;

        let new_json = serde_json::to_value(&updated).unwrap_or(serde_json::Value::Null);

        audit_service::record(
            AuditLog::new(tenant_id, Some(operator_id), "role.update", "role")
                .with_resource_id(id)
                .with_changes(Some(old_json), Some(new_json)),
            db,
        )
        .await;

        Ok(RoleResp {
            id: updated.id,
            name: updated.name.clone(),
            code: updated.code.clone(),
            description: updated.description.clone(),
            is_system: updated.is_system,
            version: updated.version,
        })
    }

    /// Soft-delete a role by setting the `deleted_at` timestamp.
    ///
    /// System roles cannot be deleted.
    pub async fn delete(
        id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        db: &sea_orm::DatabaseConnection,
        event_bus: &EventBus,
    ) -> AuthResult<()> {
        let model = role::Entity::find_by_id(id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|r| r.tenant_id == tenant_id && r.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("角色不存在".to_string()))?;

        if model.is_system {
            return Err(AuthError::Validation("系统角色不可删除".to_string()));
        }

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

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

        Ok(())
    }

    /// Replace all permission assignments for a role.
    ///
    /// Soft-deletes existing assignments and creates new ones.
    pub async fn assign_permissions(
        role_id: Uuid,
        tenant_id: Uuid,
        operator_id: Uuid,
        permission_ids: &[Uuid],
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<()> {
        // Verify the role exists and belongs to this tenant
        let _role = role::Entity::find_by_id(role_id)
            .one(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?
            .filter(|r| r.tenant_id == tenant_id && r.deleted_at.is_none())
            .ok_or_else(|| AuthError::Validation("角色不存在".to_string()))?;

        // Soft-delete existing role_permission rows
        let existing = role_permission::Entity::find()
            .filter(role_permission::Column::RoleId.eq(role_id))
            .filter(role_permission::Column::TenantId.eq(tenant_id))
            .filter(role_permission::Column::DeletedAt.is_null())
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        let now = Utc::now();
        for rp in existing {
            let mut active: role_permission::ActiveModel = rp.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| AuthError::Validation(e.to_string()))?;
        }

        // Insert new role_permission rows
        for perm_id in permission_ids {
            let rp = role_permission::ActiveModel {
                role_id: Set(role_id),
                permission_id: Set(*perm_id),
                tenant_id: Set(tenant_id),
                data_scope: Set("all".to_string()),
                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),
            };
            rp.insert(db)
                .await
                .map_err(|e| AuthError::Validation(e.to_string()))?;
        }
        Ok(())
    }

    /// Fetch all permissions assigned to a role.
    ///
    /// Resolves through the role_permission join table.
    pub async fn get_role_permissions(
        role_id: Uuid,
        tenant_id: Uuid,
        db: &sea_orm::DatabaseConnection,
    ) -> AuthResult<Vec<PermissionResp>> {
        let rp_rows = role_permission::Entity::find()
            .filter(role_permission::Column::RoleId.eq(role_id))
            .filter(role_permission::Column::TenantId.eq(tenant_id))
            .filter(role_permission::Column::DeletedAt.is_null())
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        let perm_ids: Vec<Uuid> = rp_rows.iter().map(|rp| rp.permission_id).collect();
        if perm_ids.is_empty() {
            return Ok(vec![]);
        }

        let perms = permission::Entity::find()
            .filter(permission::Column::Id.is_in(perm_ids))
            .filter(permission::Column::TenantId.eq(tenant_id))
            .all(db)
            .await
            .map_err(|e| AuthError::Validation(e.to_string()))?;

        Ok(perms
            .iter()
            .map(|p| PermissionResp {
                id: p.id,
                code: p.code.clone(),
                name: p.name.clone(),
                resource: p.resource.clone(),
                action: p.action.clone(),
                description: p.description.clone(),
            })
            .collect())
    }
}
