from ..variables.loader import Datavars
from ..commands.commands import Command
from ..logging import dictLogConfig
from logging.config import dictConfig
from logging import getLogger
from typing import Callable
from fastapi import FastAPI
from .worker import Worker
import importlib
import uvicorn
import asyncio
import os


# TODO
# 1. Разобраться с описанием команд как ресурсов и со всем, что от них зависит.
# 2. Разобраться с объектами воркеров. И способом их функционирования.


class Server:
    def __init__(self, socket_path='./input.sock',
                 datavars_path: str = 'calculate/vars/',
                 commands_path: str = 'calculate/commands'):
        self._app = FastAPI()
        self._socket_path = socket_path
        self._event_loop = asyncio.get_event_loop()

        # Конфигурируем логгирование.
        dictConfig(dictLogConfig)
        self._logger = getLogger("main")
        self.log_msg = {'DEBUG': self._logger.debug,
                        'INFO': self._logger.info,
                        'WARNING': self._logger.warning,
                        'ERROR': self._logger.error,
                        'CRITICAL': self._logger.critical}

        self._datavars = Datavars(variables_path=datavars_path,
                                  logger=self._logger)

        # Словарь описаний команд.
        self._commands = self._get_commands_list(commands_path)

        # Словарь CID и экземпляров команд, передаваемых воркерам.
        self._commands_instances = {}

        # Словарь WID и экземпляров процессов-воркеров, передаваемых воркерам.
        self._workers = {}

        # Соответствие путей обработчикам запросов для HTTP-метода GET.
        self._add_routes(self._app.get,
                         {"/": self._get_root,
                          "/commands": self._get_commands,
                          "/commands/{cid}": self._get_command,
                          "/workers/{wid}": self._get_worker})
        self._add_routes(self._app.post,
                         {"/commands/{command_id}": self._post_command})

    def _get_commands_list(self, commands_path: str) -> list:
        '''Метод для получения совокупности описаний команд.'''
        output = {}
        package = ".".join(commands_path.split("/"))

        for entry in os.scandir(commands_path):
            if (not entry.name.endswith('.py')
               or entry.name in {"commands.py", "__init__.py"}):
                continue
            module_name = entry.name[:-3]
            try:
                module = importlib.import_module("{}.{}".format(package,
                                                                module_name))
                for obj in dir(module):
                    if type(module.__getattribute__(obj)) == Command:
                        command_object = module.__getattribute__(obj)
                        output[command_object.id] = command_object
            except Exception:
                continue
        return output

    # Обработчики запросов серверу.
    async def _get_root(self) -> dict:
        '''Обработчик корневых запросов.'''
        return {'msg': 'root msg'}

    async def _get_commands(self) -> dict:
        '''Обработчик, отвечающий на запросы списка команд.'''
        response = {}
        for command_id, command_object in self._commands.items():
            response.update({command_id: {"title": command_object.title,
                                          "category": command_object.category,
                                          "icon": command_object.icon,
                                          "command": command_object.command}})
        return response

    async def _get_command(self, cid: int) -> dict:
        '''Обработчик запросов списка команд.'''
        if cid not in self._commands_instances:
            # TODO добавить какую-то обработку ошибки.
            pass
        return {'id': cid,
                'name': f'command_{cid}'}

    async def _get_worker(self, wid: int):
        '''Тестовый обработчик.'''
        self._make_worker(wid=wid)
        worker = self._workers[wid]
        worker.run(None)
        await worker.send({"text": "INFO"})
        data = await worker.get()
        if data['type'] == 'log':
            self.log_msg[data['level']](data['msg'])
        return data

    async def _post_command(self, command_id: str) -> int:
        if command_id not in self._commands:
            # TODO добавить какую-то обработку ошибки.
            pass
        return

    # Обработчики сообщений воркеров.

    # Вспомогательные методы.
    def _add_routes(self, method: Callable, routes: dict) -> None:
        '''Метод для добавления методов.'''
        for path, handler in routes.items():
            router = method(path)
            router(handler)

    def _make_worker(self, wid: int = None):
        '''Метод для создания воркера для команды.'''
        if wid is not None:
            self._workers[wid] = Worker(wid, self._event_loop)
            return wid
        elif not self._workers:
            self._workers[0] = Worker(0, self._event_loop)
            return 0
        else:
            wid = max(self._workers.keys()) + 1
            self._workers[wid] = Worker(wid, self._event_loop)
            return wid

    def _make_command(self, command_id: str) -> int:
        '''Метод для создания команды по ее описанию.'''
        command_description = self._commands[command]

    @property
    def app(self):
        return self._app

    def run(self):
        '''Метод для запуска сервера.'''
        # Выгружаем список команд.
        uvicorn.run(self._app,
                    uds=self._socket_path)


if __name__ == '__main__':
    server = Server()
    server.run()
