# -*- coding: utf-8 -*-
import json

import requests
import sqlite3

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

from application.model import (
    UpdateData,
    UpdateStatus,
    UpdateType,
)
from application.model.configuration import Configurations
from application.model.customexception import (
    ErrorInsertingNewUpdateVersion,
    ErrorUpdatingStep,
)
from mbcontextmessagehandler import MbContextMessageBus
from mwhelper import (
    BaseRepository,
)
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)

    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_update_as_applied(
        self,                       # type: BinaryUpdaterPOSRepository
        update_id,                  # type: str
        backoffice_version,        # type: str
    ):
        # type: (...) -> None

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

                datetime_now = str(datetime.now().isoformat())
                query = """
                    UPDATE UpdatesController
                    SET
                        AppliedDate = '{0}',
                        ServerUpdateIdentifier = '{1}'
                    WHERE
                        Id = '{2}'
                        AND TypeId = '{3}'
                """.format(datetime_now, json.dumps(backoffice_version), 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 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: () -> UpdateData

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

            query = """
                SELECT
                    Id,
                    UpdateName,
                    AppliedDate,
                    NotifiedDate,
                    ServerUpdateIdentifier
                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:
                return UpdateData(
                    update_id=int(row.get_entry("Id")),
                    update_name=row.get_entry("UpdateName"),
                    applied_date=row.get_entry("AppliedDate"),
                    notified_date=row.get_entry("NotifiedDate"),
                    server_update_identifier=row.get_entry("ServerUpdateIdentifier"),
                )

            return UpdateData(
                update_id=0,
            )

        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 invalidate_last_binary_update(
        self,       # type: BinaryUpdaterPOSRepository
    ):
        # type: (...) -> None

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

                query = """
                    UPDATE UpdatesController
                    SET AppliedDate = NULL, NotifiedDate = NULL
                    WHERE
                        Id = (
                            SELECT Max(Id)
                            FROM UpdatesController
                            WHERE
                                TypeId = '{}'
                            ORDER BY Id DESC
                        )
                """.format(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 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

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