import stat
from io import StringIO
from pathlib import Path
from typing import Tuple, Optional

from clarabot_nano_common.common.error import log_escape_str
from clarabot_nano_common.common.handler.base import NanoHandlerError
from clarabot_nano_common.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND,
)
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.common.protocol.response import NanoBaseResponse

from clarabot_nano.common.request.server_document import DocumentWsOpenRequest
from clarabot_nano.server.document.config import DocumentServerConfig
from clarabot_nano.server.document.handler.base import AbstractDocumentFileSystemHandler, DocumentServerHandleHelper


class DocumentWsOpenResponse(NanoBaseResponse):
    SESSION = "session"  # when creating a new session


class DocumentWsOpenHandler(AbstractDocumentFileSystemHandler):
    REJECTS_ANONYMOUS_IDENTITY = False  # Further authorization is by room membership and group flags.
    EXECUTION_STATE_CAN_CONFLICT = False  # cacheable and request_key are constant, no conflict is possible
    HANDLER_KEY = DocumentWsOpenRequest.TYPE_KEY

    def __init__(
        self, helper: DocumentServerHandleHelper, extracted_config: DocumentServerConfig, context: dict, payload: dict
    ):
        AbstractDocumentFileSystemHandler.__init__(self, helper, extracted_config, context, payload)
        self.wsd_ws_url = self.payload.get(DocumentWsOpenRequest.WSD_WS_URL)
        if not isinstance(self.wsd_ws_url, str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid wsd_ws_url", self)
        self.template = self.payload.get(DocumentWsOpenRequest.TEMPLATE)
        if self.template is not None and not isinstance(self.template, str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid template", self)
        self.session = None

    async def _create_request_key(self, secure: bool = False) -> Tuple[bool, Optional[list], int]:
        return self._secure_request_key(False, None, 1) if secure else False, None, 1

    async def _execute_initial(self) -> dict:
        d = await AbstractDocumentFileSystemHandler._execute_initial(self)
        try:
            file_path, account_id, room_id, can_write, ws_url = self.helper.parse_wsd_ws_url(self.wsd_ws_url)
        except Exception as e:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, f"Invalid WSD url: {e}", self)
        if account_id != self.context[NanoRequestContext.ACCOUNT_ID]:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Inconsistent account", self)
        if room_id != self.context[NanoRequestContext.ROOM][NanoRequestContext.ROOM_ID]:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Inconsistent room", self)
        if can_write != self.can_write_drive():
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Inconsistent write permission", self)
        if self.template:
            template_source = self.helper.validate_template_source_path(file_path, self.template)
            if not template_source:
                raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Non existent template", self)
        else:
            template_source = None
        # Verify path from file_id is in the drive, exists and is a file.
        resolved_path = Path(file_path)
        try:
            paths, s = self.resolve_chroot_paths_stat(
                self.chroot, resolved_path.relative_to(self.chroot.root).as_posix()
            )
        except ValueError:  # Path not in drive.
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, "Invalid path", self)
        if resolved_path != paths[-1][0]:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_TARGET_NOT_FOUND, "Inconsistent path resolution", self)
        if not stat.S_ISREG(s.st_mode):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Not a file", self)
        if template_source:
            self.helper.store_template_source(file_path, account_id, room_id, template_source)
        self.session = await self.helper.document_manager.create_document_session(
            account_id, room_id, can_write, ws_url
        )
        d[DocumentWsOpenResponse.SESSION] = self.session
        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"Open document session {log_escape_str(self.session)}"

    def build_error_message(self, buf: StringIO) -> None:
        buf.write("Open document session")
