from io import StringIO
from typing import Tuple, Optional

from clarabot_crypto.group_config.block import GroupConfigBlockMemberFlags
from clarabot_nano_common.common.error import maybe_rad32, log_escape_str
from clarabot_nano_common.common.handler.error import NanoHandlerError
from clarabot_nano_common.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_DENIED,
)
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.common.protocol.response import NanoBaseResponse
from clarabot_nano_common.utility.algorithm import dict_nested_get

from clarabot_nano.common.request.gateway import NanoCreateAdhocTokenRequest
from clarabot_nano.gateway.config import GatewayConfig
from clarabot_nano.gateway.handler.filesystem import AbstractFileSystemGatewayHandler
from clarabot_nano.gateway.state.gateway import GatewayState


class CreateAdhocTokenResponse(NanoBaseResponse):
    TOKEN = "token"
    KEY1 = "key1"


class CreateAdhocTokenHandler(AbstractFileSystemGatewayHandler):
    EXECUTION_STATE_CAN_CONFLICT = False  # cacheable and request_key are constant, no conflict is possible

    HANDLER_KEY = NanoCreateAdhocTokenRequest.TYPE_KEY

    TTL_MIN = 5 * 60
    TTL_MAX = 365 * 24 * 60 * 60

    def __init__(self, helper: GatewayState, extracted_config: GatewayConfig, context: dict, payload: dict):
        AbstractFileSystemGatewayHandler.__init__(self, helper, extracted_config, context, payload)
        if not self.context.get(NanoRequestContext.ROOM):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Room is missing", self)
        if self.extracted_config.deny_anonymous:  # Do not produce tokens if they would be rejected at use.
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_DENIED, "Deny anonymous", self)
        # NOTE: Since this handler requires mutation-id and authenticates the requester as the owner or an active
        # member, all adhoc-tokens will only ever be made for identities with access to the given room. From this
        # point on it is the requester's prerogative to whom they share the token with, granting them anonymous access.
        # NOTE: The token includes the identity of the creator, which is used to check their access and deny it at
        # validation if they are no longer a member.
        if self.context[NanoRequestContext.OWNER_ACCOUNT_ID] == self.context[NanoRequestContext.ACCOUNT_ID]:
            pass  # owner may always create adhoc tokens
        else:
            # check if member is active (not anonymous)
            config_member_flags = self.context[NanoRequestContext.ROOM][NanoRequestContext.ROOM_CONFIG_MEMBER]
            if not config_member_flags or not (config_member_flags & GroupConfigBlockMemberFlags.ACTIVE):
                raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_DENIED, "No permission", self)
        # prepare arguments
        self.fs_path = self.payload.get(NanoCreateAdhocTokenRequest.FS_PATH)
        if not isinstance(self.fs_path, str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Missing path", self)
        ttl = self.payload.get(NanoCreateAdhocTokenRequest.TTL)
        if ttl is None:
            pass
        elif isinstance(ttl, int) and self.TTL_MIN <= ttl <= self.TTL_MAX:
            pass
        else:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid ttl", self)
        self.ttl = ttl
        # these will be saved for logging
        self.room_id = None

    async def _create_request_key(self, secure: bool = False) -> Tuple[bool, Optional[list], int]:
        # NOTE: We resolve the path to exist, but do not restrict it targeting the root of the drive. Such is useful
        # for selecting a few items in the root directory for adhoc inter-nano copy.
        self.resolve_chroot_paths_stat(self.chroot, self.fs_path)
        return self._secure_request_key(False, None, 1) if secure else False, None, 1

    async def _execute_initial(self) -> dict:
        self.room_id = dict_nested_get(self.context, NanoRequestContext.ROOM, NanoRequestContext.ROOM_ID)
        token, secret = await self.helper.create_fs_adhoc_token(
            self.room_id, self.context[NanoRequestContext.ACCOUNT_ID], self.fs_path, self.ttl
        )
        d = await AbstractFileSystemGatewayHandler._execute_initial(self)
        d[CreateAdhocTokenResponse.TOKEN] = token
        d[CreateAdhocTokenResponse.KEY1] = secret
        return d

    async def _execute_part(self, part: int, s_hmac_start_part: Optional[int]) -> dict:  # pragma: no cover
        raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Part request is invalid", self)

    def _log_message(self) -> str:
        return (
            f"Create adhoc token FS for room {maybe_rad32(self.room_id)} at {log_escape_str(self.fs_path)} "
            f"with ttl {'infinite' if not self.ttl else self.ttl}"
        )

    def build_error_message(self, buf: StringIO) -> None:
        buf.write("Create adhoc token FS for room ")
        try:
            room_id = dict_nested_get(self.context, NanoRequestContext.ROOM, NanoRequestContext.ROOM_ID)
        except KeyError:
            pass
        else:
            buf.write(maybe_rad32(room_id))
