import logging
import stat
from io import StringIO
from typing import Tuple, Optional

from clarabot_nano_common.common.error import 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_TEMPORARY_FAILURE,
)
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.common.protocol.response import NanoBaseResponse
from clarabot_nano_common.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano_common.server.general.config import GeneralServerConfig

from clarabot_nano.common.request.server_document import DocumentWsPrepareRequest
from clarabot_nano.server.document.handler.base import DocumentServerHandleHelper, AbstractDocumentFileSystemHandler

logger = logging.getLogger(__name__)


class NanoDocumentWsPrepareResponse(NanoBaseResponse):
    WSD_VERSION_HASH = "wvh"
    WOPI_FILE_ID = "wfi"
    WOPI_ACCESS_TOKEN = "wat"
    OPEN_REQUIRES_TEMPLATE = "ort"


class DocumentWsPrepareHandler(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 = DocumentWsPrepareRequest.TYPE_KEY

    def __init__(
        self, helper: DocumentServerHandleHelper, extracted_config: GeneralServerConfig, context: dict, payload: dict
    ):
        AbstractDocumentFileSystemHandler.__init__(self, helper, extracted_config, context, payload)
        if not isinstance(self.payload.get(DocumentWsPrepareRequest.PATH), str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Missing path", self)
        self.path = 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:
        paths, s = self.resolve_chroot_paths_stat(self.chroot, self.payload[DocumentWsPrepareRequest.PATH])
        if not stat.S_ISREG(s.st_mode):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Not a file", self)
        self.path = paths[-1][0]
        # TODO: ext/mimetype check for support in discovery?
        try:
            wsd_version_hash = await self.helper.get_wsd_version_hash()
        except Exception as e:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE, "WSD discovery failed", self) from e
        file_id, access_token = self.helper.create_wopi_url_args(
            self.path,
            self.context[NanoRequestContext.ACCOUNT_ID],
            self.context[NanoRequestContext.ROOM][NanoRequestContext.ROOM_ID],
            self.can_write_drive(),
        )
        d = await AbstractNanoServerHandler._execute_initial(self)
        d[NanoDocumentWsPrepareResponse.WSD_VERSION_HASH] = wsd_version_hash
        # NOTE: Normally the editor client load URL with WOPI src URL as a query parameter would be returned, but
        # for us the webapp can decide on how to load the editor from the available editor clients, so we only
        # return the file-id. All the other URL parts are irrelevant for us, because we perform a proxy-translation
        # to the local WSD service. The structure of the URLs are kept though, so the editor clients stay compatible.
        # The client can produce the load url like:
        #   wopi_src_url = f"https://<any-or-invalid-host>/wopi/files/{file_id}"
        #   src_url = f"https://doc.clarabot.com/browser/{wsd_version_hash}/cool.html?"
        #   load_url = f"{src_url}WOPISrc={url_param_encode(wopi_src_url)}"
        d[NanoDocumentWsPrepareResponse.WOPI_FILE_ID] = file_id
        # The access-token normally gets POST-ed as a form field to the load_url by the client, but we will pass it
        # to the editor iframe with a fragment instead, to avoid revealing it to the server. (and avoid having to
        # implement POST-form parsing on the server and injecting the field into the main HTML file)
        d[NanoDocumentWsPrepareResponse.WOPI_ACCESS_TOKEN] = access_token
        # Warn the caller that this file is empty and can't be opened without an explicit template.
        if s.st_size == 0 and not self.helper.validate_template_source_path(self.path.as_posix(), "empty"):
            d[NanoDocumentWsPrepareResponse.OPEN_REQUIRES_TEMPLATE] = True
        return d

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

    def _log_message(self) -> str:
        return f"Prepare document session for {log_escape_str(self.path)}"

    def build_error_message(self, buf: StringIO) -> None:
        buf.write("Prepare document session for ")
        buf.write(log_escape_str(self.payload.get(DocumentWsPrepareRequest.PATH)))
