import typing
import asyncio
import functools
import concurrent

import naz

import prometheus_client


class BenchmarksHook(naz.hooks.BaseHook):
    """
    This is an implementation of BaseHook.

    When this hook is called by `naz` it sends metrics to prometheus.
    """

    def __init__(self) -> None:
        self.registry = prometheus_client.CollectorRegistry()
        _labels = ["project", "smpp_command", "state", "response_code"]
        self.counter = prometheus_client.Counter(
            name="number_of_messages",
            documentation="number of messages processed by naz.",
            labelnames=_labels,
            registry=self.registry,
        )
        self._LOOP: typing.Union[None, asyncio.events.AbstractEventLoop] = None
        self.thread_name_prefix = "naz_benchmarks_hook_pool"

        # go to prometheus dashboard(http://localhost:9000/) & you can run queries like:
        # 1. container_memory_rss{name="naz_cli", container_label_com_docker_compose_service="naz_cli"}
        # 2. container_memory_rss{name=~"naz_cli|message_producer"}
        # 3. number_of_messages_total{project="naz_benchmarks"}
        # 4. rate(number_of_messages_total{smpp_command="submit_sm",state="request"}[30s])  # msg sending over the past 30seconds

    def _get_loop(self) -> asyncio.events.AbstractEventLoop:
        if self._LOOP:
            return self._LOOP

        try:
            loop: asyncio.events.AbstractEventLoop = asyncio.get_running_loop()
        except RuntimeError:
            loop = asyncio.get_event_loop()
        except Exception as e:
            raise e

        # cache event loop
        self._LOOP = loop
        return self._LOOP

    async def to_smsc(self, smpp_command: str, log_id: str, hook_metadata: str, pdu: bytes) -> None:
        self.counter.labels(
            project="naz_benchmarks",
            smpp_command=smpp_command,
            state="request",
            response_code="",  # this is a request so there's no response_code
        ).inc()
        with concurrent.futures.ThreadPoolExecutor(
            thread_name_prefix=self.thread_name_prefix
        ) as executor:
            await self._get_loop().run_in_executor(executor, functools.partial(self._publish))

    async def from_smsc(
        self,
        smpp_command: str,
        log_id: str,
        hook_metadata: str,
        status: "naz.state.CommandStatus",
        pdu: bytes,
    ) -> None:
        self.counter.labels(
            project="naz_benchmarks",
            smpp_command=smpp_command,
            state="response",
            response_code=status.code,
        ).inc()  # Increment by 1
        with concurrent.futures.ThreadPoolExecutor(
            thread_name_prefix=self.thread_name_prefix
        ) as executor:
            await self._get_loop().run_in_executor(executor, functools.partial(self._publish))

    def _publish(self):
        """
        push metrics out to a place where prometheus can scrape.
        """
        prometheus_client.push_to_gateway(
            "push_to_gateway:9091", job="BenchmarksHook", registry=self.registry
        )