import logging
import sqlite3
from sqlite3 import Connection
from threading import Condition
from typing import (
    ContextManager,
    Type,
    Optional,
)

from event_manager.async_response import (
    AsyncResponseProcessor,
    AsyncResponseInteractor,
)
from event_manager.get_abandoned import GetAbandonedProcessor
from event_manager.get_details import GetDetailsProcessor
from event_manager.get_pending import GetPendingProcessor
from event_manager.publish_event import (
    PublishEventProcessor,
    PublishEventInteractor,
)
from event_manager_api.shared.token import (
    TK_EVENT_MANAGER_PUBLISH,
    TK_EVENT_MANAGER_GET_PENDING,
    TK_EVENT_MANAGER_GET_ABANDONED,
    TK_EVENT_MANAGER_GET_DETAILS,
)
from model import MaintenanceService
from timeutil import (
    RealSleeper,
    Sleeper,
)
from ._ConfigInfo import ConfigInfo
from messagebus import (
    Message,
    Event,
    MessageBus,
)
from messagehandler import MessageHandlerBuilder
from messageprocessor import (
    MessageProcessorMessageHandler,
    DefaultMessageProcessorExecutorFactory,
    MessageProcessorCallback,
)
from messageprocessorutil import LoggingProcessorCallback
from model import (
    EventRepositoryFactory,
    NewEventTopic,
    PublishQueue,
    Publisher,
    TargetedEventsAsyncPublisher,
    AsyncTargetReplySubscriber,
    EventQueue,
)
from model.repository import EventRepository
from opentelemetry_edphelper import OpenTelemetryProcessorCallback
from sqlite_alembic_util import (
    SqliteAlembicHandler,
    AlembicDatabaseInfo,
)
from sqlite_repository import SqliteEventRepository
from timeutil import (
    Clock,
    RealClock,
)


class EventManagerMessageHandlerBuilder(MessageHandlerBuilder):
    def __init__(
        self,
        config_info: ConfigInfo,
        message_bus: MessageBus,
        clock: Clock = None,
        condition: Condition = None,
        sleeper: Sleeper = None
    ):
        self.config_info = config_info
        self.conn_string = config_info.database_path
        self.message_bus = message_bus
        self.clock = clock or RealClock()
        self.condition = condition or Condition()
        self.sleeper = sleeper or RealSleeper()

        self.alembic_handler: Optional[SqliteAlembicHandler] = None
        self.maintenance: Optional[MaintenanceService] = None
        self.event_queue: Optional[EventQueue] = None
        self.publisher: Optional[Publisher] = None
        self.new_event_topic: Optional[NewEventTopic] = None
        self.publish_queue: Optional[PublishQueue] = None
        self.targeted_async_publisher: Optional[TargetedEventsAsyncPublisher] = None
        self.async_target_reply_subscriber: Optional[AsyncTargetReplySubscriber] = None

        self.conn_mapping = {}

    def build_singletons(self):
        self.alembic_handler = SqliteAlembicHandler(
            database_infos=AlembicDatabaseInfo(
                name="event-manager",
                conn_strings=[
                    self.conn_string,
                ]
            )
        )
        self.alembic_handler.upgrade_to_head()

        self.alembic_handler = SqliteAlembicHandler(
            database_infos=AlembicDatabaseInfo(
                name="event-manager",
                conn_strings=[
                    self.conn_string,
                ]
            )
        )

        self.event_queue = EventQueue(
            clock=self.clock,
            event_repository_factory=SqliteEventRepositoryFactory(self.conn_string),
            condition=self.condition,
        )

        self.publisher = Publisher(
            message_bus=self.message_bus,
            event_processor_listener=self.event_queue,
            clock=self.clock,
            max_workers=self.config_info.max_worker_threads
        )

        self.new_event_topic = NewEventTopic()
        self.publish_queue = PublishQueue(
            publisher=self.publisher,
            event_queue=self.event_queue,
        )
        self.targeted_async_publisher = TargetedEventsAsyncPublisher(
            message_bus=self.message_bus,
        )
        self.async_target_reply_subscriber = AsyncTargetReplySubscriber(
            message_bus=self.message_bus,
            event_repository_factory=SqliteEventRepositoryFactory(self.conn_string),
        )

        self.maintenance = MaintenanceService(
            event_repository_factory=SqliteEventRepositoryFactory(self.conn_string),
            sleeper=self.sleeper,
            clock=self.clock,
            maintenance_interval=self.config_info.maintenance_interval,
            acked_events_ttl=self.config_info.acked_events_ttl,
            abandoned_events_ttl=self.config_info.abandoned_events_ttl,
        )
        self.maintenance.start()

        self.new_event_topic.add_listener(self.targeted_async_publisher)
        self.new_event_topic.add_listener(self.async_target_reply_subscriber)
        self.new_event_topic.add_listener(self.event_queue)
        self.publish_queue.start()

    def build_message_handler(
        self,
        message: Message,
        event: Event,
    ):
        conn = _connect(conn_string=self.conn_string)
        event_repository = SqliteEventRepository(conn=conn)

        publish_event_interactor = PublishEventInteractor(
            event_repository=event_repository,
            new_event_topic=self.new_event_topic,
            clock=self.clock,
        )
        async_response_interactor = AsyncResponseInteractor(
            event_queue=self.event_queue,
            event_ack_listener=self.event_queue,
            clock=self.clock,
        )
        event_processors = None
        if event is not None:
            event_processors = {
                event.subject: AsyncResponseProcessor(interactor=async_response_interactor)
            }
        message_handler = MessageProcessorMessageHandler(
            event_processors=event_processors,
            message_processors={
                TK_EVENT_MANAGER_PUBLISH: PublishEventProcessor(
                    publish_event_interactor=publish_event_interactor,
                ),
                TK_EVENT_MANAGER_GET_PENDING: GetPendingProcessor(event_repository=event_repository),
                TK_EVENT_MANAGER_GET_ABANDONED: GetAbandonedProcessor(event_repository=event_repository),
                TK_EVENT_MANAGER_GET_DETAILS: GetDetailsProcessor(event_repository=event_repository),
            },
            executor_factory=DefaultMessageProcessorExecutorFactory(
                callbacks=[
                    LoggingProcessorCallback(logger=logging.getLogger("eventmanager.LoggingProcessorCallback")),
                    SqliteConnectionTransactionCallback(conn=conn),
                    OpenTelemetryProcessorCallback(),
                ]
            ),
        )

        self.conn_mapping[message_handler] = conn

        return message_handler

    def destroy_message_handler(self, message_handler):
        if message_handler in self.conn_mapping:
            conn = self.conn_mapping[message_handler]
            conn.close()
            del self.conn_mapping[message_handler]

    def destroy_singletons(self):
        self.event_queue.stop()
        self.publish_queue.stop()
        self.publisher.finish()
        self.maintenance.stop()


class SqliteConnectionTransactionCallback(MessageProcessorCallback):
    def __init__(self, conn: Connection):
        self.conn = conn

    def process_begun(self, event_name, request_id, **kwargs):
        return

    def input_parsed(self, event_name, request_id, input_obj):
        return

    def business_called(self, event_name, request_id, input_obj, output_obj):
        self.conn.commit()

    def process_finished(self, event_name, request_id, input_obj, output_obj):
        return

    def parse_exception(self, event_name, request_id, data, exception):
        return

    def unhandled_exception(self, event_name, request_id, input_obj, exception):
        self.conn.rollback()


class SqliteConnectionContextManager(object):
    def __init__(
        self,
        conn: Connection,
        repo_type: Type,
    ):
        self.conn = conn
        self.repo_type = repo_type

    def __enter__(self):
        return self.repo_type(self.conn)

    def __exit__(self, exc_type, exc_val, exc_tb):
        try:
            if exc_type is None:
                self.conn.commit()
                return True

            self.conn.rollback()
            return False
        finally:
            self.conn.close()


class SqliteEventRepositoryFactory(EventRepositoryFactory):
    def __init__(self, conn_string: str):
        self.conn_string = conn_string

    def build(self) -> ContextManager[EventRepository]:
        return SqliteConnectionContextManager(
            conn=_connect(self.conn_string),
            repo_type=SqliteEventRepository,
        )


def _connect(conn_string: str) -> Connection:
    conn = sqlite3.connect(database=conn_string, uri=True)
    conn.execute("PRAGMA journal_mode=WAL")
    return conn
