import asyncio

import motor.motor_asyncio as motor_io
from aiohttp import web
from aioredis import create_redis_pool
from smb.common.multiruntime.lib.io import setup_filesystem

from maps_adv.common.lasagna import Lasagna
from maps_adv.common.mds import MDSClient
from maps_adv.geosmb.doorman.client import DoormanClient

from . import api, tasks
from .data_manager import DataManager
from .domain import Domain
from .domain.lock_manager import LockManager

setup_filesystem("maps_adv/geosmb/harmonist/server/")


class Application(Lasagna):
    __slots__ = "dm", "domain"

    SWIM_ENGINE_CLS = None
    MIGRATIONS_PATH = None

    TASKS = {
        "geosmb_harmonist__process_unvalidated": tasks.process_unvalidated,
        "geosmb_harmonist__process_unimported": tasks.process_unimported,
    }
    TASKS_KWARGS_KEYS = ["domain"]

    domain: Domain

    async def _setup_layers(self, _) -> web.Application:
        motor_client = motor_io.AsyncIOMotorClient(
            self.config["DATABASE_URL"], minPoolSize=5
        )
        clients = {
            "mds_client": await MDSClient(
                installation=self.config["MDS_INSTALLATION"],
                tvm_client=self.tvm,
                tvm_destination="mds",
                namespace=self.config["MDS_STORE_NAMESPACE"],
            ),
            "doorman_client": await DoormanClient(
                self.config["DOORMAN_URL"],
                tvm=self.tvm,
                tvm_destination="doorman",
            ),
        }

        dm = DataManager(
            motor_client=motor_client, db_name=self.config["DATABASE_NAME"]
        )
        redis = await create_redis_pool(self.config["REDIS_URL"])
        lock_manager = LockManager(redis=redis)
        self.domain = Domain(
            dm=dm,
            mds_client=clients["mds_client"],
            doorman_client=clients["doorman_client"],
            lock_manager=lock_manager,
        )

        _api = api.create(self.domain)
        _api.on_shutdown.append(
            lambda _: asyncio.wait(
                [
                    self._close_motor_client(motor_client),
                    *[c.close() for c in clients.values() if c is not None],
                ]
            )
        )

        async def _close_redis():
            redis.close()
            await redis.wait_closed()

        _api.on_shutdown.append(lambda _: _close_redis())

        return _api

    async def _close_motor_client(
        self, motor_client: motor_io.AsyncIOMotorClient
    ) -> None:
        motor_client.close()
