import asyncio
import logging
import math
import time

from typing import Iterable, Dict, Type

from saas.tools.devops.canary.writer.configs import StreamConfig, ToolConfig
from saas.tools.devops.canary.writer.delivery.common import DeliveryProcessor
from saas.tools.devops.canary.writer.delivery.logbroker import LogbrokerDeliveryProcessor
from saas.tools.devops.canary.writer.service import SaasServiceWrapper


class DeliveryManager:
    _delivery_type_to_processor: Dict[str, Type[DeliveryProcessor]] = {
        StreamConfig.Type.LOGBROKER: LogbrokerDeliveryProcessor
    }

    TICK_TIME_SECONDS = 1

    def __init__(self, config: ToolConfig, service: SaasServiceWrapper) -> None:
        self._config: ToolConfig = config
        self._service: SaasServiceWrapper = service

    def _get_service_streams(self) -> Iterable[StreamConfig]:
        docfetcher = self._service.rtyserver_config.docfetcher
        if not docfetcher or not docfetcher.enabled:
            raise RuntimeError('Docfetcher module is not set or enabled')

        return docfetcher.streams

    async def _start_infinite_write(self, stream_id: int, stream_type: str) -> None:
        logging.warning('Starting infinite write for %s stream %s', stream_type, stream_id)

        processor_cls = self._delivery_type_to_processor[stream_type]
        processor = processor_cls(self._config, self._service, stream_id)

        if processor.ASYNC_INIT_REQUIRED:
            await processor.async_init()

        ts = time.time()
        ts_ceil = math.ceil(ts)

        while True:
            document = processor.generate_document(url=str(ts_ceil))
            await processor.send_document(document)

            ts = time.time()
            ceil = math.ceil(ts)

            old_ts_ceil = ts_ceil
            ts_ceil = ceil if ceil > ts_ceil else ceil + self.TICK_TIME_SECONDS

            ts_ceil_delta = ts_ceil - old_ts_ceil
            if ts_ceil_delta > self.TICK_TIME_SECONDS:
                logging.error(
                    'The writer for stream (%d, %s) was unable to '
                    'process its iteration within required %ds. It took %ds instead.',
                    stream_id, stream_type, self.TICK_TIME_SECONDS, ts_ceil_delta
                )

            await asyncio.sleep(ts_ceil - ts)

    async def infinite_write(self) -> None:
        tasks = []
        for stream in self._get_service_streams():
            if stream.type == stream.Type.LOGBROKER:
                tasks.append(self._start_infinite_write(stream.id, stream.type))
            else:
                logging.warning('Stream type %s is not yet supported and will not be processed', stream.type)

        if not tasks:
            raise RuntimeError('No supported streams found')

        await asyncio.gather(*tasks)
