# -*- coding: utf-8 -*-
import requests
import sqlite3

from datetime import datetime
from requests import Response
from typing import Dict

from application.model import UpdateType, UpdateStatus
from application.model.configuration import Configurations
from application.model.customexception import ErrorInsertingNewUpdateVersion, ErrorRestartingHV, ErrorUpdatingStep
from mbcontextmessagehandler import MbContextMessageBus
from msgbus import TK_SYS_ACK
from mwhelper import (
    BaseRepository,
    restart_system,
)
from persistence import Connection

from ._CommonPOSRepository import CommonPOSRepository


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

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

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

    def insert_new_binary_update(self, update_id, update_name):
        # type: (str, str) -> None

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

                date_now = str(datetime.now().isoformat())
                query = """
                INSERT OR REPLACE INTO UpdatesController
                (UpdateId, UpdateName, TypeId, StatusId, ObtainedDate)
                VALUES ('{0}', '{1}', '{2}', '{3}', '{4}')"""
                query_format = query.format(update_id, update_name, self.update_value, UpdateStatus.pending, date_now)
                conn.query(query_format)

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

    def set_downloaded_date(self, update_id):
        self._update_step(update_id, 'DownloadedDate')

    def set_backup_date(self, update_id):
        self._update_step(update_id, 'BackupDate')

    def set_applied_date(self, update_id):
        self._update_step(update_id, 'AppliedDate')

    def set_notified_date(
        self,           # type: BinaryUpdaterPOSRepository
        update_id,      # type: str
    ):
        # type: (...) -> None

        self._update_step(
            update_id=update_id,
            update_step='NotifiedDate',
        )

    def _update_step(
        self,               # type: BinaryUpdaterPOSRepository
        update_id,          # type: int
        update_step,        # type: str
    ):
        # type: (...) -> None

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

                datetime_now = str(datetime.now().isoformat())
                query = """
                    UPDATE UpdatesController
                    SET {0} = '{1}'
                    WHERE 
                        Id = '{2}' AND 
                        TypeId = '{3}'
                """.format(update_step, datetime_now, update_id, self.update_value)
                conn.query(query)

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

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

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

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

            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 get_last_binary_update_info(self):
        # type: () -> int

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

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

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

            if row:
                update_id = row.get_entry("Id")
                notified_date = row.get_entry("NotifiedDate")
                return int(update_id), notified_date

            return 0, None

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

    def get_last_binary_update_not_applied_controller_id(self):
        # type: () -> Dict

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

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

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

            if row:
                update_id = row.get_entry("Id")
                downloaded_date = row.get_entry("DownloadedDate")
                update_name = row.get_entry("UpdateName")
                result = {
                    "update_id": update_id,
                    "downloaded_date": datetime.strptime(downloaded_date, "%Y-%m-%dT%H:%M:%S.%f"),
                    "update_name": update_name
                }
                return result

            return {}

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

    def get_last_binary_update_name(self):
        # type: () -> str

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

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

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

            if row:
                update_name = row.get_entry("UpdateName")
                return update_name

            return ""

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

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

                query = """
                            DELETE FROM UpdatesController
                            WHERE Id = '{0}' AND TypeId = '{1}'
                        """.format(update_id, self.update_value)
                conn.query(query)

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

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

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

    @staticmethod
    def download_from_url(
        url     # type: str
    ):
        # type: (...) -> Response

        return requests.get(url, allow_redirects=True)

    def download_chrome_and_save(
        self,
        download_url,   # type: str
        file_path       # type: str
    ):
        # type: (...) -> bool

        response = self.download_from_url(url=download_url)
        if response.status_code == 200:
            with open(file_path, "wb") as zip_object:
                for chunk in response.iter_content(chunk_size=128):
                    zip_object.write(chunk)
            return True
        return False

    def restart_hv(self):
        # type: () -> None
        res = restart_system(
            mb_context=self.message_bus.mbcontext,
            event_type="POS_UPDATER_BINARY",
        )
        if res.token != TK_SYS_ACK:
            raise ErrorRestartingHV("Could not perform HV global restart")

    @staticmethod
    def _create_connection(db_file):
        return sqlite3.connect(db_file)
