import asyncio
import logging
import os
from typing import Callable

from concurrent_log_handler import ConcurrentRotatingFileHandler
from ttldict2 import TTLDict

from clarabot_nano.common.handler.stream import ServerResponsePartSerializationHelper
from clarabot_nano.utility.log_reader import ConcurrentRotatingFileReader


class ManagedLogReader(ConcurrentRotatingFileReader):
    def __init__(self, handler: ConcurrentRotatingFileHandler, max_size: int):
        ConcurrentRotatingFileReader.__init__(self, handler, max_size)
        self.request_session = os.urandom(16)
        self.forward_serializer = ServerResponsePartSerializationHelper("log stream")
        self.backward_lock = asyncio.Lock()
        self.last_read = asyncio.get_running_loop().time()

    def reset_serializer(self):  # allows continuing streaming with the reader in a new stream from part 0.
        self.forward_serializer = ServerResponsePartSerializationHelper("log stream")


class LogReaderManager:
    def __init__(self, get_part_size: Callable[[], int], stream_buffer_time_slice: float = 0.25):
        self._get_part_size = get_part_size
        self._stream_buffer_time_slice = stream_buffer_time_slice
        self._readers = TTLDict(stream_buffer_time_slice * 10)
        self._clean_to = None

    async def close(self):
        if self._clean_to:
            self._clean_to.cancel()

    def check_reader(self, session: str, name: str) -> bytes:
        reader = self._readers.get((session, name), touch=True)
        if reader is None:
            raise KeyError
        self._update_clean_to()
        return reader.request_session

    def ensure_reader(self, session: str, name: str, force_new: bool = False, first_part: bool = False) -> bytes:
        key = (session, name)
        if force_new:
            self._readers.pop(key, None)
        reader = self._readers.get(key, touch=True)
        self._update_clean_to()
        if reader is None:
            self._readers[key] = reader = self._create_reader(name)
        elif first_part:
            reader.reset_serializer()
        return reader.request_session

    def _create_reader(self, name: str) -> ManagedLogReader:
        if name == "access":
            logger = logging.getLogger("nano/access-log")
        elif name == "service":
            logger = logging.getLogger(__name__)
        else:
            raise KeyError(f"Unknown logger name {name}")
        handler = None
        while logger and not handler:
            for h in logger.handlers:
                if isinstance(h, ConcurrentRotatingFileHandler):
                    handler = h
                    break
            else:
                logger = logger.parent if logger.propagate else None
        if not handler:
            raise KeyError(f"Not found handler for logger name {name}")
        return ManagedLogReader(handler, self._get_part_size())

    async def read(self, session: str, name: str, forward: bool = True, part: int = None) -> dict:
        reader = self._readers[(session, name)]
        self._update_clean_to()
        async with reader.backward_lock if part is None else reader.forward_serializer.serialize_processing(part):
            loop = asyncio.get_running_loop()
            if forward:
                delay = self._stream_buffer_time_slice + reader.last_read - loop.time()
                if delay > 0.0:
                    await asyncio.sleep(delay)
            result = await loop.run_in_executor(None, reader.read, forward)
            if forward:
                reader.last_read = loop.time()
        if result.get("reset"):
            self._readers.pop((session, name), None)
        return result

    def _update_clean_to(self):
        if self._clean_to:
            self._clean_to.cancel()
        self._clean_to = asyncio.get_running_loop().call_later(
            self._stream_buffer_time_slice * 25, self._readers.drop_old_items
        )
