import logging
import os
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.base import NanoHandlerError
from clarabot_nano_common.common.protocol.errors import NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.common.protocol.response import NanoBaseResponse
from clarabot_nano_common.common.protocol.server import NanoServerResponse

from clarabot_nano.common.request.gateway import AdminLogStreamRequest
from clarabot_nano.gateway.config import GatewayConfig
from clarabot_nano.gateway.handler.base import AbstractNanoGatewayHandler
from clarabot_nano.gateway.state.gateway import GatewayState


class LogStreamResponse(NanoBaseResponse):
    SALT = "salt"
    FORWARD = "fwd"


class AdminLogStreamHandler(AbstractNanoGatewayHandler):
    REQUIRES_OWNER_IDENTITY = True
    REQUIRES_ADMIN_SESSION = True
    EXECUTION_STATE_CAN_CONFLICT = False  # cacheable and request_key are constant, no conflict is possible
    HANDLER_KEY = AdminLogStreamRequest.TYPE_KEY
    SUCCESS_LOG_LEVEL = logging.DEBUG  # do not log this normally to avoid a feedback loop

    def __init__(self, helper: GatewayState, extracted_config: GatewayConfig, context: dict, payload: dict):
        AbstractNanoGatewayHandler.__init__(self, helper, extracted_config, context, payload)
        self.admin_session = self.payload.get(AdminLogStreamRequest.ADMIN_SESSION)
        self.name = self.payload.get(AdminLogStreamRequest.NAME)
        if not isinstance(self.name, str):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid logger name", self)
        self.first_part = not bool(self.context.get(NanoRequestContext.PART))
        # The "new" flag is only applicable to the first (initial) part.
        self.new = self.first_part and self.payload.get(AdminLogStreamRequest.NEW, False)
        if not isinstance(self.new, bool):
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid new logger", self)

    async def execute(self) -> dict:
        response = await AbstractNanoGatewayHandler.execute(self)
        response[NanoServerResponse.TOUCH_ADMIN_SESSION] = True
        return response

    async def _create_request_key(self, secure: bool = False) -> Tuple[bool, Optional[list], int]:
        try:
            logger_session = self.helper.log_readers.ensure_reader(
                self.admin_session, self.name, self.new, self.first_part
            )
        except KeyError:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Not found", self)
        r = (
            False,  # state stream is unique and live for all observers
            [self.HANDLER_KEY, self.admin_session, logger_session],
            2147483647,  # infinite, if reached the client shall just reconnect with a new consumer_id
        )
        if secure:
            return self._secure_request_key(*r)
        return r

    async def _execute_initial(self) -> dict:
        d = await AbstractNanoGatewayHandler._execute_initial(self)
        d[LogStreamResponse.SALT] = os.urandom(4)
        d[LogStreamResponse.FORWARD] = await self.helper.log_readers.read(self.admin_session, self.name, part=0)
        return d

    async def _execute_part(self, part: int, s_hmac_start_part: Optional[int]) -> dict:
        d = await AbstractNanoGatewayHandler._execute_part(self, part, s_hmac_start_part)
        d[LogStreamResponse.SALT] = os.urandom(4)
        d[LogStreamResponse.FORWARD] = await self.helper.log_readers.read(self.admin_session, self.name, part=part)
        return d

    def _log_message(self) -> str:
        return f"Stream logs {log_escape_str(self.name)} session {log_escape_str(self.admin_session)}"

    def build_error_message(self, buf: StringIO) -> None:
        buf.write("Stream logs ")
        buf.write(log_escape_str(self.payload.get(AdminLogStreamRequest.NAME)))
        if self.context.get(NanoRequestContext.ADMIN_SESSION_TTL) is not None:
            buf.write(" session ")
            buf.write(log_escape_str(self.payload.get(AdminLogStreamRequest.ADMIN_SESSION)))
