from typing import Tuple, Optional

from clarabot_nano.common.handler.base import NanoHandlerError, MaybeConflictNanoHandlerError
from clarabot_nano.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_DENIED,
)
from clarabot_nano.common.protocol.request import NanoRequestContext
from clarabot_nano.common.protocol.response import NanoBaseResponse
from clarabot_nano.common.request.server_fstx import NanoFilesystemMkdirRequest
from clarabot_nano.server.base.config import ServerConfig
from clarabot_nano.server.base.handler.base import ServerHandleHelper
from clarabot_nano.server.fstx.filesystem.operation.mkdir import MkdirOperation
from clarabot_nano.server.fstx.handler.base import AbstractFilesystemTransactorNanoHandler
from clarabot_nano.utility.path.validate import ValidationError


class NanoFilesystemMkdirResponse(NanoBaseResponse):
    NORMALIZED_NAME = "norm_name"  # may be different from the input name due to normalization


class MakeDirectorySystemHandler(AbstractFilesystemTransactorNanoHandler):
    HANDLER_KEY = NanoFilesystemMkdirRequest.TYPE_KEY

    def __init__(self, helper: ServerHandleHelper, extracted_config: ServerConfig, context: dict, payload: dict):
        AbstractFilesystemTransactorNanoHandler.__init__(self, helper, extracted_config, context, payload)
        if not isinstance(self.payload.get(NanoFilesystemMkdirRequest.PATH), str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Path is missing", self.context)
        if not self.can_write_drive():  # anonymous is denied by mutation-id requirement
            raise NanoHandlerError(
                NANO_REQUEST_ERROR_HANDLER_DENIED,
                f"No permission to make directory at {self.payload.get(NanoFilesystemMkdirRequest.PATH)}",
                self.context,
            )
        self.path = None

    async def _create_request_key(self) -> Tuple[bool, Optional[list], int]:
        return False, None, 1

    async def _execute_initial(self) -> dict:
        d = await AbstractFilesystemTransactorNanoHandler._execute_initial(self)
        try:
            self.path = self.chroot.new_path(
                self.payload[NanoFilesystemMkdirRequest.PATH],
                name_preprocess_fn=self.extracted_config.normalize_remote_path,
            )
            async with self.helper.fstx_queue.wait_ready(
                MkdirOperation(self.context[NanoRequestContext.ACCOUNT_ID], self.path.as_posix())
            ) as submitter:
                # Final check before execution. This can fail if contesting operations have changed the filesystem.
                if self.path != self.chroot.new_path(
                    self.payload[NanoFilesystemMkdirRequest.PATH],
                    name_preprocess_fn=self.extracted_config.normalize_remote_path,
                ):
                    raise ValidationError("Depending paths are contested")
                await submitter()  # TODO: timeout
        except (OSError, ValidationError) as e:
            raise MaybeConflictNanoHandlerError(
                NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
                f"Creating directory failed {self.payload.get(NanoFilesystemMkdirRequest.PATH)}",
                self.context,
            ) from e
        d[NanoFilesystemMkdirResponse.NORMALIZED_NAME] = self.path.name
        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.context)

    def _log_message(self) -> str:
        return f"Make directory {self.path}"
