from typing import Optional

from api_graphql.abc.mutation_abc import MutationABC
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
from api_graphql.input.group_create_input import GroupCreateInput
from api_graphql.input.group_update_input import GroupUpdateInput
from api_graphql.require_any_resolvers import has_assigned_user_spaces
from core.logger import APILogger
from core.string import first_to_lower
from data.schemas.public.group import Group
from data.schemas.public.group_dao import groupDao
from data.schemas.public.group_role_assignment import GroupRoleAssignment
from data.schemas.public.group_role_assignment_dao import groupRoleAssignmentDao
from service.permission.permissions_enum import Permissions

logger = APILogger(__name__)


class GroupMutation(MutationABC):
    def __init__(self):
        MutationABC.__init__(self, "Group")

        self.field(
            MutationFieldBuilder("create")
            .with_resolver(self.resolve_create)
            .with_input(GroupCreateInput)
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
            .with_require_any([Permissions.groups_create], [has_assigned_user_spaces])
        )

        self.field(
            MutationFieldBuilder("update")
            .with_resolver(self.resolve_update)
            .with_input(GroupUpdateInput)
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
            .with_require_any([Permissions.groups_update], [has_assigned_user_spaces])
        )

        self.field(
            MutationFieldBuilder("delete")
            .with_resolver(self.resolve_delete)
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
            .with_require_any([Permissions.groups_delete], [has_assigned_user_spaces])
        )

        self.field(
            MutationFieldBuilder("restore")
            .with_resolver(self.resolve_restore)
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
            .with_require_any([Permissions.groups_delete], [has_assigned_user_spaces])
        )

    @staticmethod
    async def _handle_group_role_assignments(gid: int, roles: Optional[list[int]]):
        if roles is None:
            return

        existing_roles = await groupDao.get_roles(gid)
        existing_role_ids = {role.id for role in existing_roles}

        new_role_ids = set(roles)

        roles_to_add = new_role_ids - existing_role_ids
        roles_to_remove = existing_role_ids - new_role_ids

        if roles_to_add:
            group_role_assignments = [
                GroupRoleAssignment(0, gid, role_id) for role_id in roles_to_add
            ]
            await groupRoleAssignmentDao.create_many(group_role_assignments)

        if roles_to_remove:
            assignments_to_remove = await groupRoleAssignmentDao.find_by(
                [
                    {GroupRoleAssignment.group_id: gid},
                    {GroupRoleAssignment.role_id: {"in": roles_to_remove}},
                ]
            )
            await groupRoleAssignmentDao.delete_many(assignments_to_remove)

    @classmethod
    async def resolve_create(cls, obj: GroupCreateInput, *_):
        logger.debug(f"create group: {obj.__dict__}")

        already_exists = await groupDao.find_by({Group.name: obj.name})
        if len(already_exists) > 0:
            raise ValueError(f"Group {obj.name} already exists")

        group = Group(
            0,
            obj.name,
            obj.user_space_id,
        )

        gid = await groupDao.create(group)

        await cls._handle_group_role_assignments(gid, obj.roles)

        return await groupDao.get_by_id(gid)

    @classmethod
    async def resolve_update(cls, obj: GroupUpdateInput, *_):
        logger.debug(f"update group: {input}")

        if await groupDao.find_by_id(obj.id) is None:
            raise ValueError(f"Group with id {obj.id} not found")

        if obj.name is not None:
            already_exists = await groupDao.find_by({Group.name: obj.name})
            if len(already_exists) > 0:
                raise ValueError(f"Group {obj.name} already exists")

            group = await groupDao.get_by_id(obj.id)
            group.name = obj.name
            await groupDao.update(group)

        await cls._handle_group_role_assignments(obj.id, obj.roles)

        return await groupDao.get_by_id(obj.id)

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

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