from datetime import datetime

from application.model import UpdateType, UpdateData, UpdateStep, UpdateStatus
from application.model.configuration import Configurations
from application.model.customexception import (
    ErrorInsertingNewUpdateVersion,
    ErrorUpdatingStep,
    ErrorUpdatingStatus,
)
from mbcontextmessagehandler import MbContextMessageBus
from messagebus import Message, DataType, TokenCreator
from mwhelper import (
    BaseRepository,
)
from persistence import Connection
from typing import Optional, Dict
from ._CommonPOSRepository import CommonPOSRepository

TK_RUPTURA_CLEAN_WITH_CATALOG_UPDATE = TokenCreator.create_token("0", "42", "11")


class CatalogUpdaterPOSRepository(BaseRepository):
    def __init__(self, message_bus, configs):
        # type: (MbContextMessageBus, Configurations) -> None

        super(CatalogUpdaterPOSRepository, self).__init__(message_bus.mbcontext)

        self.message_bus = message_bus
        self.configs = configs
        self.catalog_update_value = UpdateType.catalog.value
        self.catalog_json_update_value = UpdateType.catalog_json.value
        self.local_configs = configs.updaters[UpdateType.catalog.name]
        self.common_pos_repository = CommonPOSRepository(message_bus)

    def get_last_downloaded_version(self, type_id):
        # type: (str) -> Dict

        def inner_func(conn):
            # type: (Connection) -> Optional[int]

            update_value = self.catalog_update_value if type_id is None else type_id.value
            query = """SELECT UpdateId, UpdateName
                       FROM UpdatesController
                       WHERE TypeId = '{0}'
                        AND DownloadedDate IS NOT NULL
                        AND StatusId <> '{1}'
                       ORDER BY Id DESC
                       LIMIT 1""".format(update_value, UpdateStatus.error)

            cursor = conn.select(query)
            if not cursor.rows():
                return {}

            update_id = cursor.get_row(0).get_entry("UpdateId")
            update_name = cursor.get_row(0).get_entry("UpdateName")
            return {"updateId": update_id, "updateName": update_name}

        return self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)

    def has_catalog_pending_update_to_apply(self):
        # type: () -> Optional[UpdateData]

        def inner_func(conn):
            # type: (Connection) -> Optional[UpdateData]

            query = """
                SELECT
                    UpdateId,
                    UpdateName,
                    ObtainedDate,
                    DownloadedDate,
                    BackupDate,
                    AppliedDate,
                    NotifiedDate
                FROM UpdatesController
                    WHERE TypeId = '1' AND AppliedDate IS NULL
                    ORDER BY Id DESC
                LIMIT 1
            """

            cursor = conn.select(query)
            row = cursor.get_row(0)
            update_data = None

            if row:
                update_data = UpdateData()
                update_data.update_id = row.get_entry("UpdateId")
                update_data.update_name = row.get_entry("UpdateName")
                update_data.obtained_date = row.get_entry("ObtainedDate")
                update_data.downloaded_date = row.get_entry("DownloadedDate")
                update_data.backup_date = row.get_entry("BackupDate")
                update_data.applied_date = row.get_entry("AppliedDate")
                update_data.notified_date = row.get_entry("NotifiedDate")

            return update_data

        return self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)

    def has_already_applied_update(self):
        # type: () -> bool

        return self.common_pos_repository.has_already_applied_update(
            self.configs.persistence_name,
            self.catalog_update_value
        )

    def insert_new_update(self, update_id, update_name, type_id=None):
        # type: (str, str, UpdateType) -> None

        try:
            def inner_func(conn):
                # type: (Connection) -> None

                update_value = self.catalog_update_value if type_id is None else type_id.value
                obtained_date = str(datetime.now().isoformat())
                conn.query("""INSERT OR REPLACE INTO UpdatesController
                              (UpdateId, UpdateName, TypeId, StatusId, ObtainedDate)
                              VALUES ('{0}', '{1}', '{2}', '{3}', '{4}')
                           """.format(update_id, update_name, update_value, UpdateStatus.pending, obtained_date))

            self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)
        except Exception as ex:
            raise ErrorInsertingNewUpdateVersion(ex)

    def skip_pending_updates(self, update_type):
        # type: (str) -> None

        try:
            def inner_func(conn):
                # type: (Connection) -> None

                datetime_now = str(datetime.now().isoformat())
                conn.query(
                    """
                        UPDATE UpdatesController
                        SET StatusId = '{0}',
                        BackupDate = '{1}',
                        AppliedDate = '{1}',
                        NotifiedDate = '{1}'
                        WHERE AppliedDate IS NULL AND TypeId = {2}
                        """.format(UpdateStatus.skipped, datetime_now, update_type))

            self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)
        except Exception as ex:
            raise ErrorInsertingNewUpdateVersion(ex)

    def get_pending_update_to_notify(self):
        # type: () -> Optional[str]

        def inner_func(conn):
            # type: (Connection) -> Optional[str]

            query = """
                        SELECT Id, UpdateId
                        FROM UpdatesController
                        WHERE TypeId = 1 AND AppliedDate IS NOT NULL AND NotifiedDate is NULL
                        ORDER BY Id DESC LIMIT 1
                    """

            cursor = conn.select(query)
            if not cursor.rows():
                return None, None

            database_id = cursor.get_row(0).get_entry("Id")
            update_id = cursor.get_row(0).get_entry("UpdateId")
            return database_id, update_id

        return self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)

    def update_step(self, update_id, update_step, type_id=None):
        # type: (int, UpdateStep, UpdateType) -> None

        try:
            def inner_func(conn):
                # type: (Connection) -> None

                update_value = self.catalog_update_value if type_id is None else type_id.value
                datetime_now = str(datetime.now().isoformat())

                if update_step == UpdateStep.all:
                    conn.query(
                        """UPDATE UpdatesController
                               SET ObtainedDate = IFNULL(ObtainedDate, '{0}'),
                                   DownloadedDate = IFNULL(DownloadedDate, '{0}'),
                                   BackupDate = IFNULL(BackupDate, '{0}'),
                                   AppliedDate = IFNULL(AppliedDate, '{0}'),
                                   NotifiedDate = IFNULL(NotifiedDate, '{0}')
                               WHERE UpdateId = '{1}' AND TypeId = '{2}'
                            """.format(datetime_now, update_id, update_value)
                    )
                elif update_step == UpdateStep.notify:
                    conn.query(
                        """
                                UPDATE UpdatesController
                                SET '{0}' = '{1}'
                                WHERE
                                    TypeId = '{2}' AND
                                    NotifiedDate IS NULL
                            """.format(update_step, datetime_now, update_value)
                    )
                else:
                    conn.query(
                        """UPDATE UpdatesController
                               SET '{0}' = '{1}'
                               WHERE UpdateId = '{2}' AND TypeId = '{3}'
                            """.format(update_step, datetime_now, update_id, update_value)
                    )

            self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)
        except Exception as ex:
            raise ErrorUpdatingStep(ex)

    def update_status(self, update_id, update_status):
        # type: (int, UpdateStatus) -> None
        try:
            def inner_func(conn):
                # type: (Connection) -> None

                conn.query(
                    """
                        UPDATE UpdatesController
                        SET
                            StatusId = '{0}'
                        WHERE
                            UpdateId = '{1}' AND
                            TypeId = '{2}'
                    """.format(update_status, update_id, self.catalog_update_value)
                )

            self.execute_with_connection(inner_func, service_name=self.configs.persistence_name)
        except Exception as ex:
            raise ErrorUpdatingStatus(ex)
