import asyncio
import copy
import hashlib
import os
from io import StringIO
from typing import Tuple, Optional

from clarabot_nano_common.common.handler.error import NanoHandlerError
from clarabot_nano_common.common.protocol.errors import NANO_REQUEST_ERROR_DEBUG
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.server.base.handler.base import AbstractNanoServerHandler, ServerHandleHelper
from clarabot_nano_common.utility.serialize import uint2bytes, msgpack_dumps


class DebugServerHandleHelper(ServerHandleHelper):
    def __init__(self, pool, dbg: dict):
        ServerHandleHelper.__init__(self, pool)
        self.dbg = dbg


class _AbstractDebugNanoHandler(AbstractNanoServerHandler):
    def _debug_data_source_state(self):
        if hasattr(self.helper, "dbg"):
            if self.helper.dbg.get("data_source_fail"):
                if not isinstance(self.payload.get("data_source_state"), int):
                    self.payload["data_source_state"] = 1
                else:
                    self.payload["data_source_state"] += 1
            if isinstance(pf := self.helper.dbg.get("processing_fail"), dict):
                if pf.get("unexpected"):
                    raise Exception("DBG unexpected")
                elif pf.get("maybe_conflict"):
                    raise NanoHandlerError(NANO_REQUEST_ERROR_DEBUG, "mb conflict debug", self, maybe_conflict=True)
                raise NanoHandlerError(NANO_REQUEST_ERROR_DEBUG, "debug", self)

    def _s_hmac_data(self):
        if hasattr(self.helper, "dbg"):
            if isinstance(data := self.helper.dbg.get("s_hmac_data"), bytes):
                return data

    def _response_context(self):
        context = copy.deepcopy(self.context)
        for kr_key in [NanoRequestContext.OWNER_KR, NanoRequestContext.PEER_KR, NanoRequestContext.ADHOC_KR]:
            if context.get(kr_key):
                context[kr_key] = context[kr_key].dump_internal()
        if room := context.get(NanoRequestContext.ROOM):
            room[NanoRequestContext.ROOM_KR] = room[NanoRequestContext.ROOM_KR].dump_internal()
        return context

    def build_error_message(self, buf: StringIO) -> None:
        buf.write(self._log_message())


class DebugServerEchoSPHandler(_AbstractDebugNanoHandler):
    HANDLER_KEY = "debug-echo-sp"

    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 _AbstractDebugNanoHandler._execute_initial(self)
        d["context"] = self._response_context()
        d["payload"] = self.payload
        self._debug_data_source_state()
        return d

    async def _execute_part(self, part: int, s_hmac_start_part: Optional[int]) -> dict:
        raise NanoHandlerError(NANO_REQUEST_ERROR_DEBUG, "Invalid", self)

    def _log_message(self) -> str:
        return "Debug echo single-part"


class DebugServerEchoCHandler(_AbstractDebugNanoHandler):
    HANDLER_KEY = "debug-echo-c"

    async def _create_request_key(self, secure: bool = False) -> Tuple[bool, Optional[list], int]:
        cacheable = bool(self.payload.get("cacheable"))
        count = int(self.payload.get("count", 2 if cacheable else 1))
        data_source_state = self.payload.get("data_source_state")
        req = cacheable, [1, cacheable, count, data_source_state], count
        return self._secure_request_key(*req) if secure else req

    async def _execute_initial(self) -> dict:
        d = await _AbstractDebugNanoHandler._execute_initial(self)
        d["context"] = self._response_context()
        d["payload"] = self.payload
        await asyncio.sleep(self.payload.get("delay", 0))
        self._debug_data_source_state()
        return d

    async def _execute_part(self, part: int, s_hmac_start_part: Optional[int]) -> dict:
        await asyncio.sleep(self.payload.get("delay", 0))
        self._debug_data_source_state()
        d = await _AbstractDebugNanoHandler._execute_part(self, part, s_hmac_start_part)
        d["context"] = self._response_context()
        padding = b"p" * int(self.context[NanoRequestContext.PART_SIZE] * 0.85 - len(msgpack_dumps(self.payload)))
        d["payload"] = copy.deepcopy(self.payload)
        d["payload"]["padding"] = padding
        if data := self._s_hmac_data():
            salt = os.urandom(16)
            data += uint2bytes(part)
            fingerprint = hashlib.sha512(salt + data).digest()[:32]
            self._add_s_hmac(d, len(data), fingerprint, salt)
        return d

    def _log_message(self) -> str:
        return "Debug echo complex"


class DebugServerEchoCAdminHandler(DebugServerEchoCHandler):
    HANDLER_KEY = "debug-echo-c-admin"
    REQUIRES_OWNER_IDENTITY = True
    REQUIRES_ADMIN_SESSION = True

    def _log_message(self) -> str:
        return "Debug echo complex admin"
