from uuid import uuid4

from api.middleware.request import get_request
from api.route import Route
from api_graphql.abc.mutation_abc import MutationABC
from api_graphql.field.mutation_field_builder import MutationFieldBuilder
from api_graphql.input.user_space_create_input import UserSpaceCreateInput
from api_graphql.input.user_space_update_input import UserSpaceUpdateInput
from api_graphql.service.query_context import QueryContext
from core.logger import APILogger
from core.string import first_to_lower
from data.schemas.administration.user_dao import userDao
from data.schemas.public.user_invitation import UserInvitation
from data.schemas.public.user_invitation_dao import userInvitationDao
from data.schemas.public.user_space import UserSpace
from data.schemas.public.user_space_dao import userSpaceDao
from data.schemas.public.user_space_user import UserSpaceUser
from data.schemas.public.user_space_user_dao import userSpaceUserDao
from service.mail_service import mailService
from service.permission.permissions_enum import Permissions

logger = APILogger(__name__)


class UserSpaceMutation(MutationABC):
    def __init__(self):
        MutationABC.__init__(self, "UserSpace")

        self.field(
            MutationFieldBuilder("create")
            .with_resolver(self.resolve_create)
            .with_input(UserSpaceCreateInput)
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
        )

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

        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.user_spaces_delete],
                [self._resolve_input_user_space_assigned],
            )
        )

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

        self.field(
            MutationFieldBuilder("inviteUsers")
            .with_resolver(self.resolve_invite_users)
            .with_input(list, "emails")
            .with_change_broadcast(
                f"{first_to_lower(self.name.replace("Mutation", ""))}Change"
            )
            .with_require_any(
                [Permissions.user_spaces_update],
                [self._resolve_input_user_space_assigned],
            )
        )

    async def resolve_create(self, obj: UserSpaceCreateInput, *_):
        logger.debug(f"create user_space: {obj.__dict__}")

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

        user_space = UserSpace(0, obj.name, (await Route.get_user()).id)
        gid = await userSpaceDao.create(user_space)

        await self._resolve_assignments(
            obj.get("users", []),
            user_space,
            UserSpaceUser.user_space_id,
            UserSpaceUser.user_id,
            userSpaceDao,
            userSpaceUserDao,
            UserSpaceUser,
            userDao,
        )

        return await userSpaceDao.get_by_id(gid)

    async def resolve_update(self, obj: UserSpaceUpdateInput, *_):
        logger.debug(f"update user_space: {input}")

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

        user_space = await userSpaceDao.get_by_id(obj.id)
        if obj.name is not None:
            already_exists = await userSpaceDao.find_by({UserSpace.name: obj.name})
            if len(already_exists) > 0:
                raise ValueError(f"UserSpace {obj.name} already exists")

            user_space.name = obj.name
            await userSpaceDao.update(user_space)

        users = obj.get("users", [])
        users_by_invitation = await userSpaceUserDao.find_by(
            [
                {UserSpaceUser.user_space_id: user_space.id},
                {UserSpaceUser.invitation: {"isNotNull": True}},
            ]
        )
        if len(users_by_invitation) > 0:
            users.extend([x.user_id for x in users_by_invitation])

        await self._resolve_assignments(
            users,
            user_space,
            UserSpaceUser.user_space_id,
            UserSpaceUser.user_id,
            userSpaceDao,
            userSpaceUserDao,
            UserSpaceUser,
            userDao,
        )

        return await userSpaceDao.get_by_id(obj.id)

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

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

    @staticmethod
    async def resolve_invite_users(emails: list[str], *_, id: str):
        logger.debug(f"invite users to user_space: {id} with emails: {emails}")

        user_space = await userSpaceDao.get_by_id(id)
        user_space_user_emails = [
            x.email for x in await userSpaceDao.get_users(user_space.id)
        ]

        email_invitations = {}

        base_url = str(get_request().base_url)
        for email in emails:
            if email in user_space_user_emails:
                continue
            user = await userDao.find_single_by({"email": {"equal": email}})

            invitation = uuid4()
            if user is None:
                user_invitation = await userInvitationDao.find_by(
                    [
                        {UserInvitation.email: email},
                        {UserInvitation.user_space_id: user_space.id},
                    ]
                )
                if len(user_invitation) > 0:
                    continue

                await userInvitationDao.create(
                    UserInvitation(
                        0,
                        email,
                        str(invitation),
                        user_space.id,
                    )
                )
                email_invitations[email] = (
                    f"{base_url}api/invitation/accept/{invitation}"
                )
                continue

            email_invitations[email] = (
                f"{base_url}api/invitation/user-space/accept/{invitation}"
            )
            await userSpaceUserDao.create(
                UserSpaceUser(0, user_space.id, user.id, str(invitation))
            )

        for email, invitation in email_invitations.items():
            logger.debug(f"Invitation for {email}: {invitation}")
            mailService.send(
                email,
                "You're invited to join Open-Redirect",
                f"Hi,\n\nYou’ve been invited to join Open-Redirect, a secure collaboration platform for your team. "
                f"To complete your registration, please use the following invitation code:\n\n"
                f"{invitation}\n\n"
                "If you weren’t expecting this invitation, you can safely ignore this message.\n\n"
                "Thanks,\n"
                "The Open-Redirect Team",
            )

        return True

    @staticmethod
    async def _resolve_input_user_space_assigned(ctx: QueryContext):
        check_dict = ctx.kwargs
        if "input" in ctx.kwargs:
            check_dict = ctx.kwargs["input"]

        return "id" in check_dict and check_dict["id"] in [
            x.id for x in await userSpaceDao.get_assigned_by_user_id(ctx.user.id)
        ]
