import asyncio
from typing import Optional

from aiohttp import web

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

from maps_adv.adv_store.v2.lib.core.direct_moderation import (
    Client as DirectModerationClient,
)
from maps_adv.adv_store.v2.lib.data_managers import (
    CampaignsDataManager,
    EventsDataManager,
    ModerationDataManager,
)
from maps_adv.adv_store.v2.lib.domains import (
    CampaignsDomain,
    EventsDomain,
    ModerationDomain,
)
from maps_adv.billing_proxy.client import Client as BillingProxyClient
from maps_adv.warden.client.lib import PeriodicalTask, TaskMaster

from . import api
from .core.logbroker import LogbrokerClient
from .db import DB

setup_filesystem("maps_adv/adv_store/v2/")


class Application:
    __slots__ = ("config", "campaign_domain", "moderation_domain")

    config: dict
    campaign_domain: CampaignsDomain
    moderation_domain: ModerationDomain

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

    def setup(
        self,
        db: DB,
        logbroker_client: Optional[LogbrokerClient],
        billing_proxy_client=None,
    ) -> web.Application:

        campaigns_dm = CampaignsDataManager(
            db,
            dashboard_api_url=self.config["DASHBOARD_API_URL"],
            yt_cluster=self.config["YT_CLUSTER"],
            yt_token=self.config["YT_TOKEN"],
            change_logs_table=self.config["YT_CHANGE_LOGS_TABLE"],
        )
        moderation_dm = ModerationDataManager(db)
        events_dm = EventsDataManager(db)

        direct_moderation_client = None
        if self.config["USE_DIRECT_MODERATION"]:
            direct_moderation_client = DirectModerationClient(
                out_topic=self.config["LOGBROKER_DIRECT_MODERATION_TOPIC_OUT"],
                in_topic=self.config["LOGBROKER_DIRECT_MODERATION_TOPIC_IN"],
                consumer_id=self.config["LOGBROKER_DIRECT_MODERATION_CONSUMER_ID"],
                avatar_mds_base_url=self.config["AVATAR_MDS_BASE_URL"],
                logbroker_client=logbroker_client,
            )

        if not billing_proxy_client:
            billing_proxy_client = BillingProxyClient(self.config["BILLING_API_URL"])

        self.campaign_domain = CampaignsDomain(
            campaigns_dm,
            events_dm,
            moderation_dm,
            self.config["DASHBOARD_API_URL"],
            billing_proxy_client,
            calculate_display_chance_from_cpm=self.config[
                "EXPERIMENT_CALCULATE_DISPLAY_CHANCE_FROM_CPM"
            ],
        )
        self.moderation_domain = ModerationDomain(
            moderation_dm, campaigns_dm, direct_moderation_client, billing_proxy_client
        )
        events_domain = EventsDomain(events_dm, campaigns_dm)

        _api = api.create(
            db,
            self.campaign_domain,
            self.moderation_domain,
            events_domain,
            ping_v1_url=self.config["PING_V1_URL"],
        )
        self._setup_tasks(_api)
        _api.on_shutdown.append(lambda app: logbroker_client.stop())
        return _api

    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())

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

        logbroker_client = None
        if self.config["USE_DIRECT_MODERATION"]:
            logbroker_client = LogbrokerClient(
                self.config["LOGBROKER_CLUSTER"],
                self.config["LOGBROKER_PORT"],
                self.config["LOGBROKER_AUTH_TOKEN"],
                self.config["LOGBROKER_SOURCE_ID"],
            )
            await logbroker_client.start()

        api = self.setup(db, logbroker_client)
        api.on_shutdown.append(lambda app: db.close())
        return api

    def _setup_tasks(self, _api: web.Application):
        if not self.config["WARDEN_URL"]:
            return

        tasks = [
            PeriodicalTask(
                "adv_store__refresh_auto_daily_budgets",
                lambda *a: self.campaign_domain.refresh_auto_daily_budgets(),
            ),
            PeriodicalTask(
                "adv_store__backup_campaigns_change_log",
                lambda *a: self.campaign_domain.backup_campaigns_change_log(),
            ),
        ]

        if self.config["USE_DIRECT_MODERATION"]:
            tasks.extend(
                [
                    PeriodicalTask(
                        "adv_store__process_direct_moderations",
                        lambda *a: self.moderation_domain.process_new_moderations(),
                    ),
                    PeriodicalTask(
                        "adv_store__process_direct_moderation_responses",
                        lambda *a: (
                            self.moderation_domain.process_direct_moderation_responses()
                        ),
                    ),
                ]
            )

        task_master = TaskMaster(server_url=self.config["WARDEN_URL"], tasks=tasks)

        _api.on_startup.append(lambda *a: task_master.run())
        _api.on_shutdown.append(lambda *a: task_master.stop())
