from sqlalchemy.orm import Session

from app.core.exceptions import AppException
from app.core.permissions import (
    add_permission_for_role,
    get_enforcer,
    get_permissions_for_role,
    get_redis_client,
    remove_permission_for_role,
)
from app.models.role import Role
from common_logging import get_logger

logger = get_logger(__name__)


class CasbinPermissionService:
    RESOURCES = [
        "users",
        "roles",
        "knowledge_bases",
        "knowledge_categories",
        "knowledge_tags",
        "agents",
        "workflows",
        "documents",
        "audit_logs",
        "menus",
        "providers",
        "models",
    ]
    ACTIONS = ["read", "create", "update", "delete", "execute"]

    @staticmethod
    def assign_permissions(
        db: Session, role_id: int, permissions: list[dict[str, str]], tenant_id: int
    ) -> bool:
        role = db.query(Role).filter(Role.id == role_id).first()
        if not role:
            raise AppException("角色不存在", "Role not found")
        if role.is_system:
            raise AppException("系统角色权限不可修改", "System role permissions cannot be modified")
        enforcer = get_enforcer()
        role_code = f"role:{role.code}"
        domain = str(tenant_id)
        old_policies = enforcer.get_filtered_policy(0, role_code, domain)
        try:
            enforcer.remove_filtered_policy(0, role_code, domain)
            for perm in permissions:
                resource = perm.get("resource")
                action = perm.get("action")
                if not resource or not action:
                    raise ValueError(f"Invalid permission: {perm}")
                success = add_permission_for_role(role_code, tenant_id, resource, action)
                if not success:
                    raise Exception(f"Failed to add permission: {resource}:{action}")
            CasbinPermissionService._invalidate_role_cache(role_id, tenant_id)
            logger.info(f"Assigned {len(permissions)} permissions to role {role.code}")
            return True
        except Exception as e:
            logger.error(f"Permission assignment failed, rolling back: {e}")
            enforcer.remove_filtered_policy(0, role_code, domain)
            for policy in old_policies:
                enforcer.add_policy(*policy)
            raise AppException(f"权限分配失败: {str(e)}", f"Permission assignment failed: {str(e)}") from None

    @staticmethod
    def get_role_permissions(role_id: int, tenant_id: int, db: Session) -> list[dict[str, str]]:
        role = db.query(Role).filter(Role.id == role_id).first()
        if not role:
            return []
        role_code = f"role:{role.code}"
        policies = get_permissions_for_role(role_code, tenant_id)
        return [{"resource": p[2], "action": p[3]} for p in policies]

    @staticmethod
    def grant_menu_permission(db: Session, role_id: int, menu_id: int, tenant_id: int) -> bool:
        role = db.query(Role).filter(Role.id == role_id).first()
        if not role:
            raise AppException("角色不存在", "Role not found")
        resource = f"menu:{menu_id}"
        action = "view"
        role_code = f"role:{role.code}"
        success = add_permission_for_role(role_code, tenant_id, resource, action)
        if success:
            CasbinPermissionService._invalidate_role_cache(role_id, tenant_id)
        return success

    @staticmethod
    def revoke_menu_permission(db: Session, role_id: int, menu_id: int, tenant_id: int) -> bool:
        role = db.query(Role).filter(Role.id == role_id).first()
        if not role:
            raise AppException("角色不存在", "Role not found")
        resource = f"menu:{menu_id}"
        action = "view"
        role_code = f"role:{role.code}"
        success = remove_permission_for_role(role_code, tenant_id, resource, action)
        if success:
            CasbinPermissionService._invalidate_role_cache(role_id, tenant_id)
        return success

    @staticmethod
    def _invalidate_role_cache(role_id: int, tenant_id: int):
        try:
            redis_client = get_redis_client()
            pattern = f"perm:*:{tenant_id}:*:*"
            cursor = 0
            while True:
                cursor, keys = redis_client.scan(cursor, match=pattern, count=100)
                if keys:
                    redis_client.delete(*keys)
                if cursor == 0:
                    break
            logger.info(f"Invalidated permission cache for tenant {tenant_id}")
        except Exception as e:
            logger.warning(f"Failed to invalidate cache: {e}")

    @staticmethod
    def get_available_resources() -> list[dict[str, any]]:
        return [
            {"resource": resource, "actions": CasbinPermissionService.ACTIONS}
            for resource in CasbinPermissionService.RESOURCES
        ]
