import asyncio
import logging

from aiohttp import web
from aiohttp.web_runner import GracefulExit

from saas.library.python.deploy_manager_api import SaasService
from saas.library.python.saas_ctype import SaasCtype

from saas.tools.devops.canary.reader.configs import AppConfig
from saas.tools.devops.canary.reader.core import ReadManager
from saas.tools.devops.canary.reader.core.discovery import SearchProxyDiscovery
from saas.tools.devops.canary.reader.core.tvm import TvmClient
from saas.tools.devops.canary.reader.handlers import yasm_handler


class AppMaker:
    def __init__(self, config: AppConfig) -> None:
        self._config: AppConfig = config

    def make_app(self) -> web.Application:
        app = web.Application()
        app.router.add_get('/yasm', yasm_handler)

        app.on_startup.append(self._on_startup)
        app.on_cleanup.append(self._on_cleanup)
        return app

    @staticmethod
    async def _read_task(
        config: AppConfig,
        service: SaasService,
        sp_discovery: SearchProxyDiscovery,
        tvm_client: TvmClient
    ) -> None:
        try:
            await ReadManager(config, service, sp_discovery, tvm_client).infinite_read()
        except asyncio.CancelledError:
            pass
        except:
            logging.exception('Uncaught exception in the read loop, shutting down the application')
        finally:
            raise GracefulExit

    async def _on_startup(self, app) -> None:
        service = SaasService(self._config.service_ctype, self._config.service_name)
        saas_ctypes = [SaasCtype(ctype) for ctype in service.dm.ctypes]
        sp_discovery = SearchProxyDiscovery(saas_ctypes)
        tvm_client = TvmClient(self._config, saas_ctypes)

        app['reader_task'] = asyncio.create_task(self._read_task(self._config, service, sp_discovery, tvm_client))
        app['sp_discovery_task'] = asyncio.create_task(sp_discovery.run())

    @staticmethod
    async def _on_cleanup(app) -> None:
        app['reader_task'].cancel()
        app['sp_discovery_task'].cancel()

        await asyncio.gather(*[app['reader_task'], app['sp_discovery_task']])
