import sqlite3

from datetime import datetime

import msgbus

from application.model.customexception import ErrorRestartingHV
from application.util import LoggerUtil
from typing import Optional, List
from sqlite3 import (
    Cursor,
    Connection as SqliteConnection,
)

from mwhelper import BaseRepository
from poshelper import restart_system
from persistence import Connection


from application.model import (
    Payment,
    PosSaleTypeNavigation,
)

logger = LoggerUtil.get_logger_name(__name__)


class CommonPOSRepository(BaseRepository):
    def __init__(
        self,
        message_bus,
    ):
        super(CommonPOSRepository, self).__init__(message_bus.mbcontext)

        self.message_bus = message_bus

    def has_already_applied_update(
        self,
        persistence_name,   # type: str
        update_value,       # type: str
    ):
        # type: (...) -> bool

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

            query = """
                SELECT COUNT(*) as VersionsApplied
                FROM UpdatesController
                WHERE TypeId = '{0}' AND AppliedDate IS NOT NULL
            """.format(update_value)

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

            try:
                if row:
                    versions_applied = row.get_entry("VersionsApplied")
                    return int(versions_applied) > 0
            except (Exception,):
                return False

        return self.execute_with_connection(inner_func, service_name=persistence_name)

    def get_last_update_date(self, persistence_name, update_value):
        # type: (str, str) -> Optional[datetime]

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

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

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

            if row:
                applied_date = row.get_entry("AppliedDate")[:19]
                return datetime.strptime(applied_date, "%Y-%m-%dT%H:%M:%S")

            return None

        return self.execute_with_connection(inner_func, service_name=persistence_name)

    def delete_all_payments(
        self,               # type: CommonPOSRepository
        database_path,      # type: str
    ):
        # type: (str) -> None

        query = """
            DELETE
            FROM TenderType
        """

        conn = self._create_connection(database_path)
        try:
            cur = conn.cursor()
            cur.execute(query)
            conn.commit()
        finally:
            conn.close()

    def insert_payments(self, database_path, payments):
        # type: (str, List[Payment]) -> None

        conn = self._create_connection(database_path)
        try:
            cursor = conn.cursor()
            self._check_custom_column(cursor)

            for payment in payments:
                open_drawer = int(payment.open_drawer)
                sql = """
                    INSERT INTO TenderType VALUES
                    ({}, '{}', '{}', NULL, 0, NULL, {}, {}, {}, {}, {}, '{}')
                """.format(
                    payment.payment_id,
                    payment.name,
                    payment.currency,
                    payment.change_limit,
                    payment.electronic_type,
                    open_drawer,
                    payment.need_auth,
                    payment.parent_id or "NULL",
                    payment.custom_attr or "{}"
                )

                cursor.execute(sql)

            conn.commit()
        finally:
            conn.close()

    def delete_all_pos_navigation_sale_type(
        self,               # type: CommonPOSRepository
        database_path,      # type: str
    ):
        # type: (...) -> None

        query = """
            DELETE
            FROM PosSaleTypeNavigation
        """

        conn = self._create_connection(database_path)
        try:
            cur = conn.cursor()
            cur.execute(query)
            conn.commit()
        finally:
            conn.close()

    def insert_pos_navigation_sale_type(
        self,               # type: CommonPOSRepository
        database_path,      # type: str
        navigations,        # type: List[PosSaleTypeNavigation]
    ):
        # type: (...) -> None

        conn = self._create_connection(database_path)
        try:
            cursor = conn.cursor()
            self._check_custom_column(cursor)

            for navigation in navigations:
                sql = """
                INSERT INTO PosSaleTypeNavigation VALUES (
                    '{}',
                    '{}',
                    '{}'
                )
                """.format(
                    navigation.pos_id,
                    navigation.sale_type,
                    navigation.navigation_id,
                )

                cursor.execute(sql)

            conn.commit()
        finally:
            conn.close()

    def table_exists_in_db(
        self,                   # type: CommonPOSRepository
        database_path,          # type: str
        table_name,             # type: str
    ):
        # type: (...) -> bool

        query = """
            SELECT name
            FROM sqlite_master
            WHERE
                type='table' AND
                name='{}'
        """.format(table_name)

        conn = self._create_connection(database_path)

        try:
            cur = conn.cursor()
            table = cur.execute(query).fetchall()
        finally:
            conn.close()

        return len(table) > 0

    def has_pos_sale_type_navigation_populated(
        self,               # type: CommonPOSRepository
        database_path,      # type: str
    ):
        # type: (...) -> bool

        try:
            query = """
                SELECT COUNT(*)
                FROM PosSaleTypeNavigation
            """

            conn = self._create_connection(database_path)
            line_count = conn.execute(query).fetchone()[0]
            return line_count > 0
        except (Exception,):
            return True

    @staticmethod
    def _check_custom_column(
        cursor  # type: Cursor
    ):
        # type: (...) -> None

        cursor.execute("""PRAGMA table_info('TenderType')""")
        columns = cursor.fetchall()
        has_custom_attr_column = any('CustomAttr' in column for column in columns)
        if not has_custom_attr_column:
            cursor.execute("""ALTER TABLE TenderType ADD CustomAttr NULL""")

    @staticmethod
    def _create_connection(
        db_file     # type: str
    ):
        # type: (...) -> SqliteConnection

        return sqlite3.connect(db_file)

    def restart_hv(
        self,           # type: CommonPOSRepository
        event_type,     # type: str
        pos_id="",      # type: str
        user_id="",     # type: str
    ):
        # type: (...) -> None

        logger.info("Restarting HV by {} event".format(event_type))

        res = restart_system(
            mb_context=self.message_bus.mbcontext,
            pos_id=pos_id,
            menu_manager_user=user_id,
            event_type=event_type,
        )
        if res.token != msgbus.TK_SYS_ACK:
            logger.error("Could not perform HV global restart by {} event".format(event_type))
            raise ErrorRestartingHV("Could not perform HV global restart")
