import asyncio
import aiogram
import aioredis
from abc import ABC, abstractmethod
from dataclasses import dataclass
from aiogram import Dispatcher, types, exceptions
from aiogram.contrib.fsm_storage.memory import MemoryStorage

try:
    from settings import InstanceSettings
except ModuleNotFoundError:
    from .settings import InstanceSettings


@dataclass()
class BotProperties:
    token: str
    start_text: str
    bot_id: int
    super_chat_id: int


class BotInstance(ABC):
    def __init__(self):
        self._redis: aioredis.Redis = None
        self._dp: aiogram.Dispatcher = None

    @abstractmethod
    async def _properties(self) -> BotProperties:
        raise NotImplementedError()

    def stop_polling(self):
        print("stop polling")
        self._dp.stop_polling()

    async def _setup(self):
        self._redis = await aioredis.create_redis_pool(InstanceSettings.redis_path())

        props = await self._properties()

        bot = aiogram.Bot(props.token)
        self._dp = Dispatcher(bot, storage=MemoryStorage())

        # Здесь перечислены все типы сообщений, которые бот должен пересылать
        self._dp.register_message_handler(self._receive_message, content_types=[types.ContentType.TEXT,
                                                                                types.ContentType.CONTACT,
                                                                                types.ContentType.ANIMATION,
                                                                                types.ContentType.AUDIO,
                                                                                types.ContentType.DOCUMENT,
                                                                                types.ContentType.PHOTO,
                                                                                types.ContentType.STICKER,
                                                                                types.ContentType.VIDEO,
                                                                                types.ContentType.VOICE])

    async def start_polling(self):
        print("start polling")
        await self._setup()
        await self._dp.start_polling()

    @classmethod
    def _message_unique_id(cls, bot_id: int, message_id: int) -> str:
        return f"{bot_id}_{message_id}"

    async def _receive_message(self, message: types.Message):
        """
        Получено обычное сообщение, вероятно, для пересыла в другой чат
        :param message:
        :return:
        """
        props = await self._properties()
        if message.text and message.text.startswith("/start"):
            # На команду start нужно ответить, не пересылая сообщение никуда
            await message.answer(props.start_text)
            return

        if message.chat.id != props.super_chat_id:
            # Это обычный чат: сообщение нужно переслать в супер-чат
            new_message = await message.forward(props.super_chat_id)
            await self._redis.set(self._message_unique_id(props.bot_id, new_message.message_id),
                                  message.chat.id)
        else:
            # Это супер-чат
            if message.reply_to_message:
                # Ответ из супер-чата переслать тому пользователю,
                chat_id = await self._redis.get(
                    self._message_unique_id(props.bot_id, message.reply_to_message.message_id))
                if not chat_id:
                    chat_id = message.reply_to_message.forward_from_chat
                    if not chat_id:
                        await message.reply("Невозможно переслать сообщение: автор не найден")
                        return
                chat_id = int(chat_id)
                try:
                    await message.copy_to(chat_id)
                except exceptions.MessageError:
                    await message.reply("Невозможно переслать сообщение: возможно, автор заблокировал бота")
                    return
            else:
                await message.forward(props.super_chat_id)


class FreezeBotInstance(BotInstance):
    def __init__(self, token: str, start_text: str, super_chat_id: int):
        super().__init__()

        self._props = BotProperties(token, start_text, int(token.split(":")[0]), super_chat_id)

    async def _properties(self) -> BotProperties:
        return self._props


if __name__ == '__main__':
    """
    Режим single-instance. В этом режиме не работает olgram. На сервере запускается только один feedback (instance)
    бот для пересылки сообщений. Все настройки этого бота задаются в переменных окружения на сервере. Бот работает
     в режиме polling
    """
    bot = FreezeBotInstance(
        InstanceSettings.token(),
        InstanceSettings.start_text(),
        InstanceSettings.super_chat_id()
    )
    asyncio.get_event_loop().run_until_complete(bot.start_polling())