import asyncio
import contextlib
import inspect
import itertools
import pathlib
from collections import deque
from typing import Awaitable, Callable, Dict, List

import discord
from redbot.core import commands
from redbot.core.utils import chat_formatting as chatutils, menus as menutils

MAX_CONTENT_SIZE = 2000 - len("```ini```\n\n")


def button(emoji: str):
    def decorator(func):
        try:
            func.__react_to__.append(emoji)
        except AttributeError:
            func.__react_to__ = [emoji]
        return func

    return decorator


# noinspection PyUnusedLocal
class LogScrollingMenu:

    _handlers: Dict[
        str,
        Callable[["LogScrollingMenu", discord.RawReactionActionEvent], Awaitable[None]],
    ] = {}

    def __init__(self, ctx: commands.Context, lines: List[str], page_size: int) -> None:
        self.ctx = ctx
        self.message = None

        self._lines = lines
        self._page_size = page_size
        self._end_pos = len(self._lines)
        self._start_pos = self._end_pos - page_size
        self._done_event = asyncio.Event()

    @classmethod
    async def send(
        cls,
        ctx: commands.Context,
        logfiles: List[pathlib.Path],
        page_size: int = 25,
        num_pages: int = 15,
    ):
        lines = deque(maxlen=num_pages * page_size + 2)
        for logfile_path in logfiles:
            new_lines = deque(maxlen=lines.maxlen - len(lines))
            with logfile_path.open() as fs:
                new_lines.extend(fs.readlines())
            lines = deque(
                iterable=itertools.chain(new_lines, lines), maxlen=lines.maxlen
            )
            del new_lines
            if len(lines) >= lines.maxlen:
                break
        lines.popleft()
        lines.popleft()
        lines.appendleft("# START OF LOG BUFFER\n")
        lines.append("# END OF LOG\n")

        self = cls(ctx, list(lines), page_size)

        self.ctx.bot.add_listener(self.on_raw_reaction, "on_raw_reaction_add")
        self.ctx.bot.add_listener(self.on_raw_reaction, "on_raw_reaction_remove")
        try:
            await asyncio.shield(self.wait())
        except asyncio.CancelledError:
            if not self._done_event.is_set() and self.message is not None:
                with contextlib.suppress(discord.NotFound):
                    await self.message.delete()
        finally:
            self.ctx.bot.remove_listener(self.on_raw_reaction, "on_raw_reaction_add")
            self.ctx.bot.remove_listener(self.on_raw_reaction, "on_raw_reaction_remove")

    async def wait(self) -> None:
        await self._update_message()
        await self._done_event.wait()

    async def on_raw_reaction(self, payload: discord.RawReactionActionEvent) -> None:
        if not self._same_context(payload):
            return

        try: