import asyncio
import functools
import json
import logging
import time

from concurrent.futures import Future
from typing import Optional

from saas.protos.rtyserver_pb2 import TMessage

from saas.library.persqueue.writer.python import writer as saas_writer
from saas.library.python.logbroker import SaaSConfigurationManager, SaaSLogbrokerServiceConfig
from saas.tools.devops.canary.writer.configs import ToolConfig
from saas.tools.devops.canary.writer.delivery.common import DeliveryProcessor
from saas.tools.devops.canary.writer.service import SaasServiceWrapper


class LogbrokerDeliveryProcessor(DeliveryProcessor):
    ASYNC_INIT_REQUIRED = True

    _WRITER_RETRY_SECONDS = 5
    _WRITER_TTL = 60*60*4

    _saas_cloud_ns = SaaSConfigurationManager().get_namespace('saas-cloud')

    def __init__(self, config: ToolConfig, service: SaasServiceWrapper, stream_id: int) -> None:
        super().__init__(config, service, stream_id)

        self._loop: asyncio.AbstractEventLoop = asyncio.get_event_loop()
        self._logbroker_cache_update: bool = False
        self._logbroker_writer: Optional[None] = None
        self._logbroker_last_writer_ts: int = -1

    async def async_init(self) -> None:
        await self._reinit_saas_writer()

    def _do_reinit(self, config: SaaSLogbrokerServiceConfig) -> saas_writer.Writer:
        logging.warning('Initializing saas_writer...')

        logger = logging.getLogger()
        lb_logger = saas_writer.Logger(logger)

        search_map_settings = saas_writer.SearchMapInputSettings()
        search_map_settings.ctype = self._service.ctype
        search_map_settings.dm_host = 'saas-dm.yandex.net'

        writer = None
        while writer is None:
            settings = saas_writer.Settings()
            writer = saas_writer.Writer()

            try:
                settings.set_logger(lb_logger)
                settings.set_tvm(self._config.tvm_id, self._config.logbroker_tvm_id, self._config.tvm_secret)
                settings.set_persqueue_settings(config.logbroker.host, config.topics_path)
                settings.set_service_info(search_map_settings, self._service.name)
                writer.init(settings)
            except Exception as exc:
                logging.exception(
                    'Unable to creating logbroker writer, exc=%s, retrying in %d seconds',
                    str(exc), self._WRITER_RETRY_SECONDS
                )

                writer = None
                time.sleep(self._WRITER_RETRY_SECONDS)

        logging.warning('Initializing saas_writer... OK')
        return writer

    async def _reinit_saas_writer(self) -> None:
        if self._logbroker_cache_update:
            return

        self._logbroker_cache_update = True

        try:
            logbroker_service = await self._saas_cloud_ns.get_service(self._service.name, self._service.ctype)
            config = await logbroker_service.get_config()

            writer = await self._loop.run_in_executor(None, lambda: self._do_reinit(config))
        except Exception:
            logging.exception('Unable to initialize saas_writer')
        else:
            self._logbroker_writer = writer
            self._logbroker_last_writer_ts = time.time()
        finally:
            self._logbroker_cache_update = False

    async def get_saas_writer(self) -> saas_writer.Writer:
        if self._logbroker_last_writer_ts + self._WRITER_TTL > time.time():
            return self._logbroker_writer

        logging.info('SaaS writer ttl has expired, trying to reinit it asynchronously...')
        self._loop.create_task(self._reinit_saas_writer())  # noqa: we do not await here

        return self._logbroker_writer

    def __on_document_written(self, document: TMessage, future: Future) -> None:
        logging.info(
            '(%s, %s) - The document was written, url=#%s, result=%s',
            self._service.name,
            self._service.ctype,
            document.Document.Url,
            json.dumps(future.result())
        )

    async def send_document(self, document: TMessage) -> None:
        logging.info(
            '(%s, %s) - Trying to send a document, url=#%s',
            self._service.name, self._service.ctype, document.Document.Url
        )

        writer = await self.get_saas_writer()
        writer.write_proto(document).add_done_callback(functools.partial(self.__on_document_written, document))
