import asyncio
from typing import Coroutine, Optional, Union

from aiohttp import web

from smb.common.multiruntime.lib.io import setup_filesystem

from maps_adv.warden.client.lib import PeriodicalTask, TaskMaster

from . import api
from .data_managers import ForecastsDataManager, PointsDataManager
from .db import DB

__all__ = ["Application"]

setup_filesystem("maps_adv/points/server/")


class Application:
    __slots__ = "_config", "sync_forecasts_task", "_forecasts_dm", "_task_master"

    sync_forecasts_task: Union[None, Coroutine, asyncio.Task]
    _forecasts_dm: ForecastsDataManager
    _task_master: TaskMaster

    def __init__(self, config: dict):
        self._config = config

        self.sync_forecasts_task = None

    def setup(self, db: DB) -> web.Application:
        points_dm = PointsDataManager(db)
        self._forecasts_dm = forecasts_dm = ForecastsDataManager(
            db,
            yt_config={
                "yt_token": self._config["YT_TOKEN"],
                "yt_table": self._config["YT_FORECASTS_TABLE"],
                "yt_cluster": self._config["YT_CLUSTER"],
            },
        )

        _api = api.create(db, points_dm, forecasts_dm)
        self._setup_tasks(_api)
        return _api

    def _setup_tasks(self, _api: web.Application):
        if not all(
            [
                self._config["WARDEN_URL"],
                self._config["YT_TOKEN"],
                self._config["YT_FORECASTS_TABLE"],
                self._config["YT_CLUSTER"],
            ]
        ):
            return

        self._task_master = TaskMaster(
            server_url=self._config["WARDEN_URL"],
            tasks=[PeriodicalTask("points__sync_forecasts", self._sync_forecasts_task)],
        )

        _api.on_startup.append(self._start_tasks)
        _api.on_shutdown.append(self._stop_tasks)

    async def _sync_forecasts_task(self, _):
        await self._forecasts_dm.sync_forecasts()

    async def _start_tasks(self, _):
        await self._task_master.run()

    async def _stop_tasks(self, _):
        await self._task_master.stop()

    async def _run(self) -> web.Application:
        db = await DB.create(self._config["DATABASE_URL"])

        return self.setup(db)

    def run(self, host: Optional[str] = None, port: Optional[int] = None):
        web.run_app(self._run(), host=host, port=port, loop=asyncio.get_event_loop())
