import logging
from concurrent.futures import ThreadPoolExecutor
from threading import Thread

from messagebus import (
    MessageBus,
    DefaultToken,
)
from opentelemetry import trace
from opentelemetry.trace import StatusCode
from timeutil import Clock
from . import EventProcessorListener
from ._EventPublishDetail import EventPublishDetail

tracer = trace.get_tracer(__name__)
logger = logging.getLogger(__name__)


class Publisher(object):
    def __init__(
        self,
        message_bus: MessageBus,
        event_processor_listener: EventProcessorListener,
        clock: Clock,
        max_workers: int = 10,
    ):
        self.message_bus = message_bus
        self.event_processor_listener = event_processor_listener
        self.clock = clock

        self.pool = ThreadPoolExecutor(
            max_workers=max_workers,
            thread_name_prefix="Publisher",
        )

    def publish_event(
        self,
        publish_detail: EventPublishDetail,
    ) -> None:

        call_callback = self._build_callback(publish_detail=publish_detail)

        future = self.pool.submit(self._publish_event, publish_detail)
        future.add_done_callback(fn=call_callback)

    def finish(self):
        thread = Thread(
            target=self.pool.shutdown,
            name="FinishThread",
        )
        thread.daemon = True
        logger.info("Stopping Publisher")
        thread.start()

        thread.join(timeout=2)

    def _publish_event(self, publish_detail: EventPublishDetail):
        with tracer.start_span("AsyncPublisher") as span:
            span.set_attribute("definitionId", str(publish_detail.id))

            try:
                if publish_detail.target is not None:
                    reply = self.message_bus.send_message(
                        component=publish_detail.target,
                        message=publish_detail.event.convert_to_message()
                    )
                else:
                    reply = self.message_bus.publish_event(event=publish_detail.event)

                if publish_detail.has_async_target():
                    publish_detail.calculate_next_retry_at(now=self.clock.utc_now())
                    span.set_status(StatusCode.OK)
                else:
                    if reply.token == DefaultToken.TK_SYS_ACK:
                        publish_detail.acked_at = self.clock.utc_now()
                        span.set_status(StatusCode.OK)
                    else:
                        publish_detail.calculate_next_retry_at(now=self.clock.utc_now())
                        span.set_status(StatusCode.ERROR)
            except (Exception,):
                publish_detail.calculate_next_retry_at(now=self.clock.utc_now())

    def _build_callback(
        self,
        publish_detail: EventPublishDetail,
    ):
        def finished_processing(_):
            self.event_processor_listener.event_processed(publish_detail=publish_detail)

        return finished_processing
