from api.auth.keycloak_client import Keycloak
from api_graphql.abc.mutation_abc import MutationABC
from api_graphql.input.user_create_input import UserCreateInput
from api_graphql.input.user_update_input import UserUpdateInput
from core.logger import APILogger
from data.schemas.administration.user import User
from data.schemas.administration.user_dao import userDao
from data.schemas.permission.role_user import RoleUser
from data.schemas.permission.role_user_dao import roleUserDao
from service.permission.permissions_enum import Permissions

logger = APILogger(__name__)


class UserMutation(MutationABC):
    def __init__(self):
        MutationABC.__init__(self, "User")
        self.mutation(
            "create",
            self.resolve_create,
            UserCreateInput,
            require_any_permission=[Permissions.users_create],
        )
        self.mutation(
            "update",
            self.resolve_update,
            UserUpdateInput,
            require_any_permission=[Permissions.users_update],
        )
        self.mutation(
            "delete",
            self.resolve_delete,
            require_any_permission=[Permissions.users_delete],
        )
        self.mutation(
            "restore",
            self.resolve_restore,
            require_any_permission=[Permissions.users_delete],
        )

    @staticmethod
    async def resolve_create(obj: UserCreateInput, *_):
        logger.debug(f"create user: {obj.__dict__}")

        # ensure keycloak knows a user with this keycloak_id
        # get_user should raise an exception if the user does not exist
        kc_user = Keycloak.admin.get_user(obj.keycloak_id)
        if kc_user is None:
            raise ValueError(f"Keycloak user with id {obj.keycloak_id} does not exist")

        user = User(0, obj.keycloak_id)
        await userDao.create(user)
        user = await userDao.get_by_keycloak_id(user.keycloak_id)
        await roleUserDao.create_many([RoleUser(0, user.id, x) for x in obj.roles])

        return user

    @staticmethod
    async def resolve_update(obj: UserUpdateInput, *_):
        logger.debug(f"update user: {obj.__dict__}")
        user = await userDao.get_by_id(obj.id)

        if obj.roles is not None:
            roles = await roleUserDao.get_by_user_id(user.id)

            to_delete = (
                roles
                if len(obj.roles) == 0
                else await roleUserDao.find_by(
                    [
                        {RoleUser.user_id: user.id},
                        {RoleUser.role_id: {"notIn": obj.get("roles", [])}},
                    ]
                )
            )
            role_ids = [x.role_id for x in roles]
            deleted_role_ids = [
                x.role_id
                for x in await roleUserDao.find_by(
                    [{RoleUser.user_id: user.id}, {RoleUser.deleted: True}]
                )
            ]

            to_create = [
                RoleUser(0, x, user.id)
                for x in obj.roles
                if x not in role_ids and x not in deleted_role_ids
            ]
            to_restore = [
                await roleUserDao.get_single_by(
                    [
                        {RoleUser.user_id: user.id},
                        {RoleUser.role_id: x},
                    ]
                )
                for x in obj.roles
                if x not in role_ids and x in deleted_role_ids
            ]

            if len(to_delete) > 0:
                await roleUserDao.delete_many(to_delete)

            if len(to_create) > 0:
                await roleUserDao.create_many(to_create)

            if len(to_restore) > 0:
                await roleUserDao.restore_many(to_restore)

        return user

    @staticmethod
    async def resolve_delete(*_, id: int):
        logger.debug(f"delete user: {id}")
        user = await userDao.get_by_id(id)
        await userDao.delete(user)
        return True

    @staticmethod
    async def resolve_restore(*_, id: int):
        logger.debug(f"restore user: {id}")
        user = await userDao.get_by_id(id)
        await userDao.restore(user)
        return True
