# -*- coding: utf-8 -*-

from datetime import datetime

from application.model import (
    UpdateData,
    UpdateType,
    UpdateStatus,
)
from application.model.configuration import Configurations
from application.model.customexception import (
    ErrorInsertingNewUpdateVersion,
    ErrorGettingUsersList,
    ErrorUpdatingUserControlType,
)
from mbcontextmessagehandler import MbContextMessageBus
from msgbus import (
    TK_SYS_ACK,
    TK_POS_LISTUSERS,
    TK_POS_GETMODEL
)
from messagebus import Message, DataType
from mwhelper import (
    BaseRepository,
)
from persistence import Connection
from typing import (
    Any,
    Optional,
    Union
)
from xml.etree import cElementTree as etree

from ._CommonPOSRepository import CommonPOSRepository


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

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

        self.message_bus = message_bus
        self.configs = configs
        self.update_type = UpdateType.loader
        self.update_value = self.update_type.value
        self.local_configs = configs.updaters[self.update_type.name]
        self.common_pos_repository = CommonPOSRepository(message_bus)

    def insert_new_loader_update(
        self,                   # type: LoaderUpdaterPOSRepository
        update_id,              # type: str
        update_name,            # type: str
        external_update_id,     # type: str
        obtained_date=None,     # type: Optional[str]
    ):
        # type: (...) -> None

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

                if external_update_id:
                    formatted_query = self.get_update_by_external_update_id_query.format(
                        external_update_id,
                        self.update_value,
                    )
                    cursor = conn.select(formatted_query)
                    row = cursor.get_row(0)

                    if row and not obtained_date:
                        return

                external_update_id_value = "'{}'".format(external_update_id) if external_update_id else "NULL"
                if obtained_date is None:
                    new_obtained_date = "'{}'".format(datetime.now().isoformat())
                    download_and_backup_date = "'{}'".format(new_obtained_date) if not external_update_id else "NULL"
                else:
                    new_obtained_date = "'{}'".format(obtained_date)
                    download_and_backup_date = new_obtained_date

                query_format = self.update_query.format(
                    update_id,
                    update_name,
                    self.update_value,
                    UpdateStatus.pending,
                    new_obtained_date,
                    download_and_backup_date,
                    external_update_id_value,
                )
                conn.query(query_format)

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

    def insert_new_remote_loader_update(
        self,                   # type: LoaderUpdaterPOSRepository
        update_id,              # type: str
        update_name,            # type: str
        external_update_id,     # type: str
    ):
        # type: (...) -> bool

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

                formatted_query = self.get_update_by_external_update_id_query.format(
                    external_update_id,
                    self.update_value,
                )
                cursor = conn.select(formatted_query)
                row = cursor.get_row(0)

                if row:
                    return False

                external_update_id_value = "'{}'".format(external_update_id)
                date_now = "'{}'".format(datetime.now().isoformat())
                download_and_backup_date = "'{}'".format(date_now) if not external_update_id else "NULL"

                query_format = self.update_query.format(
                    update_id,
                    update_name,
                    self.update_value,
                    UpdateStatus.pending,
                    date_now,
                    download_and_backup_date,
                    external_update_id_value,
                )
                conn.query(query_format)

                return True

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

    def get_last_loader_update_param(
        self,   # type: LoaderUpdaterPOSRepository
        param,  # type: str
    ):
        # type: (...) -> str

        def inner_func(
            conn,  # type: Connection
        ):
            # type: (...) -> Union[str, int]

            query = """
                SELECT *
                FROM UpdatesController
                WHERE TypeId = '{}'
                ORDER BY Id DESC
                LIMIT 1""".format(self.update_value)

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

            if row:
                return row.get_entry(param)

            return 0

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

    def get_last_loader_update(
        self,   # type: LoaderUpdaterPOSRepository
    ):
        # type: (...) -> UpdateData

        def inner_func(
            conn,  # type: Connection
        ):
            # type: (...) -> Union[str, int]

            query = """
                SELECT *
                FROM UpdatesController
                WHERE TypeId = '{}'
                ORDER BY Id DESC
                LIMIT 1""".format(self.update_value)

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

            return UpdateData(
                update_id=row.get_entry('UpdateId'),
                update_name=row.get_entry('UpdateName'),
                obtained_date=row.get_entry('ObtainedDate'),
                applied_date=row.get_entry('AppliedDate'),
                server_update_identifier=row.get_entry('ServerUpdateIdentifier'),
            )

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

    def update_pos_ctrl_type(self, pos_id, user_control_type):
        # type: (int, int) -> None

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

                query = """
                    UPDATE PosState
                    SET UserControlType = '{0}'
                    WHERE PosId = '{1}'""".format(user_control_type, pos_id)
                conn.query(query)

            self.execute_with_connection(inner_func, service_name='Persistence')
        except Exception as ex:
            raise ErrorUpdatingUserControlType(ex)

    def get_pending_loader_update_id(self):
        # type: () -> int

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

            query = """
                SELECT UpdateId
                FROM UpdatesController
                WHERE TypeId = '{0}' AND StatusId = '{1}' AND AppliedDate IS NULL
                ORDER BY Id DESC""".format(self.update_value, UpdateStatus.pending)

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

            if row:
                update_id = row.get_entry("UpdateId")
                return int(update_id)

            return 0

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

    def has_pending_loader_to_update(
        self,   # type: LoaderUpdaterPOSRepository
    ):
        # type: (...) -> bool

        def inner_func(
            conn,   # type: Connection
        ):
            # type: (...) -> bool

            query = """
                SELECT UpdateId
                FROM UpdatesController
                WHERE TypeId = '{}' AND AppliedDate IS NULL
                ORDER BY Id DESC
            """.format(self.update_value)

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

            return row is not None

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

    def delete_update_line(
        self,                  # type: LoaderUpdaterPOSRepository
        external_update_id,    # type: str
        update_id,             # type: int
    ):
        # type: (...) -> None

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

            query = """
                DELETE FROM UpdatesController
                WHERE ServerUpdateIdentifier = '{0}'
                AND UpdateId = '{1}'
                AND AppliedDate IS NULL;
            """.format(external_update_id, update_id)

            conn.select(query)

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

    def update_loader_notified_date(
        self,                  # type: LoaderUpdaterPOSRepository
        update_id,             # type: int
    ):
        # type: (...) -> None

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

            date_now = str(datetime.now().isoformat())
            query = """
                UPDATE UpdatesController
                SET NotifiedDate = '{0}'
                WHERE UpdateId <= '{1}' AND TypeId = 4;
            """.format(date_now, update_id)

            conn.select(query)

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

    def mark_loader_update_as_applied(
            self,
    ):
        # type: (...) -> None

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

                date_now = str(datetime.now().isoformat())
                query = """
                        UPDATE UpdatesController
                        SET StatusId = '{0}', AppliedDate = '{1}'
                        WHERE UpdateName = 'Loaders' AND AppliedDate IS NULL
                        """.format(UpdateStatus.applied, date_now)

                conn.query(query)

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

    def mark_loader_update_as_error(
            self,
    ):
        # type: (...) -> None

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

                query = """
                        UPDATE UpdatesController
                        SET StatusId = '{0}'
                        WHERE UpdateName = 'Loaders' AND AppliedDate IS NULL
                        """.format(UpdateStatus.error)

                conn.query(query)

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

    def has_already_applied_update(
        self,   # type: LoaderUpdaterPOSRepository
    ):
        # type: (...) -> bool

        return self.common_pos_repository.has_already_applied_update(
            persistence_name=self.configs.persistence_name,
            update_value=self.update_value,
        )

    def get_last_loader_update_date(
        self,   # type: LoaderUpdaterPOSRepository
    ):
        # type: (...) -> Optional[datetime]

        return self.common_pos_repository.get_last_update_date(
            persistence_name=self.configs.persistence_name,
            update_value=self.update_value,
        )

    def get_pos_users(
        self,       # type: LoaderUpdaterPOSRepository
        pos_id,     # type: str
    ):
        # type: (...) -> str

        message = Message(
            token=TK_POS_LISTUSERS,
            data_type=DataType.param,
            data="{}".format(pos_id),
        )
        res = self.message_bus.send_message(
            component="POS{}".format(pos_id),
            message=message,
        )
        if res.token != TK_SYS_ACK:
            raise ErrorGettingUsersList("Could not list users in POS{}".format(pos_id))
        return res.data

    def has_pending_loader_update(
        self,   # type: LoaderUpdaterPOSRepository
    ):
        # type: (...) -> bool

        try:
            def inner_func(
                conn,   # type: Connection
            ):
                # type: (...) -> bool

                query = """
                    SELECT
                        NotifiedDate
                    FROM
                        UpdatesController
                    WHERE
                        TypeId = {0}
                        AND NotifiedDate IS NULL
                        AND AppliedDate IS NOT NULL
                        AND UpdateId = (
                            SELECT
                                MAX(UpdateId)
                            FROM
                                UpdatesController
                            WHERE
                                TypeId = {0}
                        )
                """.format(self.update_value)

                cursor = conn.select(query)
                row = cursor.get_row(0)
                if row:
                    return True
                return False

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

    def get_model(
        self,
        pos_id    # type: Any[Union[str, int]]
    ):
        # type: (...) -> etree.XML

        message = Message(TK_POS_GETMODEL, data_type=DataType.param, data="{}".format(pos_id))
        res = self.message_bus.send_message("POS{}".format(pos_id), message)
        if res.token != TK_SYS_ACK:
            raise ErrorGettingUsersList("Could not list users in POS{}".format(pos_id))
        return etree.XML(res.data)

    update_query = """
        INSERT INTO UpdatesController
        (
            UpdateId,
            UpdateName,
            TypeId,
            StatusId,
            ObtainedDate,
            DownloadedDate,
            BackupDate,
            ServerUpdateIdentifier
        )
        VALUES ('{0}', '{1}', '{2}', '{3}', {4}, {5}, {5}, {6})
    """

    get_update_by_external_update_id_query = """
        SELECT ServerUpdateIdentifier
        FROM UpdatesController
        WHERE ServerUpdateIdentifier = '{0}'
        AND TypeId = {1}
    """
