import asyncio
from uuid import uuid4

from maps_adv.stat_controller.client.lib import charger, collector, normalizer
from maps_adv.stat_controller.client.lib.base import BaseClient, Conflict, NoTasksFound
from maps_adv.stat_tasks_starter.lib.base.pipeline import BasePipeline

from .charger import ChargerPipeline
from .collector import Pipeline as CollectorPipeline
from .config import config
from .normalizer import NormalizerPipeline
from .not_spending_budget import NotSpendingBudgetTask


class BaseExecutor:
    __slots__ = "id", "_pipeline"

    pipeline_cls = BasePipeline
    client_cls = BaseClient

    def __init__(self, stat_client: BaseClient):
        self.id = uuid4()
        self._pipeline = self.pipeline_cls(self.id, stat_client)

    async def __call__(self):
        await asyncio.wait_for(self.execute(), timeout=config.TIMEOUT)

    async def execute(self):
        try:
            await self._pipeline()
        except (asyncio.CancelledError, Conflict):
            pass
        except NoTasksFound:
            await asyncio.sleep(config.RELAUNCH_INTERVAL)
        except Exception:
            # FIXME(@sivakov512): Ugly hack to avoid controller DDOSing
            await asyncio.sleep(config.RELAUNCH_INTERVAL)
            raise

    @classmethod
    async def schedule(cls, interval: int):
        async with cls.client_cls(
            url=config.STAT_CONTROLLER_URL,
            retry_settings={
                "max_attempts": config.RETRY_MAX_ATTEMPTS,
                "wait_multiplier": config.RETRY_WAIT_MULTIPLIER,
            },
        ) as client:
            while True:
                await cls(client)()


class Normalizer(BaseExecutor):
    pipeline_cls = NormalizerPipeline
    client_cls = normalizer.Client


class Charger(BaseExecutor):
    pipeline_cls = ChargerPipeline
    client_cls = charger.Client


class Collector(BaseExecutor):
    pipeline_cls = CollectorPipeline
    client_cls = collector.Client


async def not_spending_budget():
    task = NotSpendingBudgetTask(config)
    await task()
