import logging
from typing import Tuple
from mongoengine import QuerySet

from spaceone.core import utils, cache
from spaceone.core.manager import BaseManager
from spaceone.core.connector.space_connector import SpaceConnector
from spaceone.identity.model.role.database import Role
from spaceone.identity.manager.role_binding_manager import RoleBindingManager
from spaceone.identity.manager.managed_resource_manager import ManagedResourceManager
from spaceone.identity.error.error_role import ERROR_ROLE_IN_USED

_LOGGER = logging.getLogger(__name__)


class RoleManager(BaseManager):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.role_model = Role

    def create_role(self, params: dict) -> Role:
        def _rollback(vo: Role):
            _LOGGER.info(f'[create_role._rollback] Delete role: {vo.name} ({vo.role_id})')
            vo.delete()

        if 'role_id' not in params:
            params['role_id'] = utils.generate_id('role')

        if api_permissions := params.get('api_permissions'):
            params['api_permissions'] = list(set(api_permissions))

        role_vo = self.role_model.create(params)
        self.transaction.add_rollback(_rollback, role_vo)

        return role_vo

    def update_role_by_vo(
            self, params: dict, role_vo: Role
    ) -> Role:
        def _rollback(old_data):
            _LOGGER.info(f'[update_role_by_vo._rollback] Revert Data: {old_data["role_id"]}')
            role_vo.update(old_data)

        if api_permissions := params.get('api_permissions'):
            params['api_permissions'] = list(set(api_permissions))

        self.transaction.add_rollback(_rollback, role_vo.to_dict())

        return role_vo.update(params)

    @staticmethod
    def delete_role_by_vo(role_vo: Role) -> None:
        rb_mgr = RoleBindingManager()
        rb_vos = rb_mgr.filter_role_bindings(role_id=role_vo.role_id, domain_id=role_vo.domain_id)

        for rb_vo in rb_vos:
            raise ERROR_ROLE_IN_USED(role_binding_id=rb_vo.role_binding_id, user_id=rb_vo.user_id)

        role_vo.delete()

    def get_role(self, role_id: str, domain_id: str) -> Role:
        return self.role_model.get(role_id=role_id, domain_id=domain_id)

    def filter_roles(self, **conditions) -> QuerySet:
        return self.role_model.filter(**conditions)

    def list_roles(self, query: dict, domain_id: str) -> Tuple[QuerySet, int]:
        self._create_managed_role(domain_id)
        return self.role_model.query(**query)

    def stat_roles(self, query: dict) -> dict:
        return self.role_model.stat(**query)

    @cache.cacheable(key='identity:managed-role:{domain_id}:sync', expire=300)
    def _create_managed_role(self, domain_id: str) -> bool:
        managed_resource_mgr = ManagedResourceManager()

        role_vos = self.filter_roles(domain_id=domain_id, is_managed=True)

        installed_role_version_map = {}
        for role_vo in role_vos:
            installed_role_version_map[role_vo.role_id] = role_vo.version

        managed_role_map = managed_resource_mgr.get_managed_roles()

        for managed_role_id, managed_role_info in managed_role_map.items():
            managed_role_info['domain_id'] = domain_id
            managed_role_info['is_managed'] = True

            if role_version := installed_role_version_map.get(managed_role_id):
                if role_version != managed_role_info['version']:
                    _LOGGER.debug(f'[_create_managed_role] update managed role: {managed_role_id}')
                    role_vo = self.get_role(managed_role_id, domain_id)
                    self.update_role_by_vo(managed_role_info, role_vo)
            else:
                _LOGGER.debug(f'[_create_managed_role] create new managed role: {managed_role_id}')
                self.create_role(managed_role_info)

        return True
