import logging
import sqlite3
from sqlite3 import Connection
from typing import (
    Dict,
    Optional,
)

from boss.interactor.authenticate import AuthenticateInteractor
from boss.interactor.authenticate_nfc import AuthenticateNfcInteractor
from boss.interactor.get_info import GetInfoInteractor
from boss.interactor.get_users_list import GetUsersListInteractor
from boss.interactor.insert_or_update_users import InsertOrUpdateUsersInteractor
from boss.interactor.insert_or_update_users.dto import InsertOrUpdateUsersRequest
from boss.model.service import AuthenticationService
from boss.processor import (
    AuthenticateNfcProcessor,
    AuthenticateProcessor,
    GetInfoProcessor,
    GetUsersListProcessor,
    InsertOrUpdateUsersProcessor,
)
from boss_api.shared import token
from messagehandler import (
    MessageHandler,
    MessageHandlerBuilder,
)
from messageprocessor import (
    DefaultMessageProcessorExecutorFactory,
    MessageProcessorMessageHandler,
)
from messageprocessorutil import LoggingProcessorCallback
from repositoryimpl.sqlite import (
    SqliteConnectionTransactionCallback,
    UserRepositoryImpl,
)
from sqlite_alembic_util import (
    AlembicDatabaseInfo,
    SqliteAlembicHandler,
)
from . import Configs

logger = logging.getLogger(__name__)


class BossMessageHandlerBuilder(MessageHandlerBuilder):

    def __init__(
        self,
        configs: Configs,
        authenticate_service: AuthenticationService,
    ) -> None:

        self.configs = configs
        self.conn_mapping: Dict[MessageHandler, Connection] = {}
        self.alembic_handler: Optional[SqliteAlembicHandler] = None

        self.authenticate_service: AuthenticationService = authenticate_service

    def build_singletons(
        self,
    ) -> None:

        self.alembic_handler = SqliteAlembicHandler(
            database_infos=AlembicDatabaseInfo(
                name="users",
                conn_strings=[
                    self.configs.database_path,
                ]
            )
        )
        self.alembic_handler.upgrade_to_head()

        self._insert_default_users()

    def build_message_handler(
        self,
        message: str,
        event: str,
    ) -> MessageHandler:

        conn = sqlite3.connect(
            database=self.configs.database_path,
            uri=True,
        )
        user_db_repository = UserRepositoryImpl(connection=conn)

        authenticate_interactor = AuthenticateInteractor(
            user_repository=user_db_repository,
            authenticate_service=self.authenticate_service,
            otp_interval=self.configs.otp_interval,
        )
        authenticate_processor = AuthenticateProcessor(interactor=authenticate_interactor)
        authenticate_nfc_interactor = AuthenticateNfcInteractor(user_repository=user_db_repository)
        authenticate_nfc_processor = AuthenticateNfcProcessor(interactor=authenticate_nfc_interactor)
        get_info_interactor = GetInfoInteractor(user_repository=user_db_repository)
        get_info_processor = GetInfoProcessor(interactor=get_info_interactor)
        get_users_list_interactor = GetUsersListInteractor(user_repository=user_db_repository)
        get_users_list_processor = GetUsersListProcessor(interactor=get_users_list_interactor)
        insert_or_update_users_interactor = InsertOrUpdateUsersInteractor(user_repository=user_db_repository)
        insert_or_update_users_processor = InsertOrUpdateUsersProcessor(interactor=insert_or_update_users_interactor)

        message_handler = MessageProcessorMessageHandler(
            event_processors=None,
            message_processors={
                token.TK_USER_CONTROL_AUTHENTICATE: authenticate_processor,
                token.TK_USER_CONTROL_AUTHENTICATE_NFC: authenticate_nfc_processor,
                token.TK_USER_CONTROL_GET_INFO: get_info_processor,
                token.TK_USER_CONTROL_GET_USERS_LIST: get_users_list_processor,
                token.TK_USER_CONTROL_INSERT_OR_UPDATE_USERS: insert_or_update_users_processor,
            },
            executor_factory=DefaultMessageProcessorExecutorFactory(
                callbacks=[
                    LoggingProcessorCallback(logger=logging.getLogger("boss.LoggingProcessorCallback")),
                    SqliteConnectionTransactionCallback(conn=conn),
                ]
            ),
        )

        self.conn_mapping[message_handler] = conn

        return message_handler

    def destroy_message_handler(
        self,
        message_handler: MessageHandler,
    ) -> None:

        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,
    ) -> None:

        return

    def _insert_default_users(
        self,
    ) -> None:

        conn = sqlite3.connect(
            database=self.configs.database_path,
            uri=True,
        )
        try:
            user_db_repository = UserRepositoryImpl(connection=conn)
            insert_or_update_users_interactor = InsertOrUpdateUsersInteractor(user_repository=user_db_repository)
            users_to_insert = InsertOrUpdateUsersRequest(users_to_insert_or_update=self.configs.default_users)
            insert_or_update_users_interactor.execute(request=users_to_insert)
            conn.commit()
        except Exception as ex:
            logger.exception("Error inserting default users: {}".format(str(ex)))
            conn.rollback()
        finally:
            conn.close()
