from zlib import compressobj, Z_DEFAULT_COMPRESSION, DEFLATED, MAX_WBITS
from base64 import b64encode

import os
import sys
import sqlite3

from xml.etree import cElementTree as ET
from tempfile import gettempdir
from shutil import copyfile
from datetime import datetime


class StandaloneExecution:
    def __init__(self, application_path, businessperiod):
        self.application_path = application_path
        self.businessperiod = businessperiod
        self.orders_to_proccess = []
        self.orders_not_found = []
        self.orders_not_processed = []
        self.server_directory = os.path.join("data", "server")
        self.update_query_boh = """DELETE FROM EVENTSSENT WHERE PERIOD >= {}""".format(self.businessperiod)
        self.eventtype = "('PAID', 'VOID_ORDER', 'ORDER_REPROCESS')"
        self.auditlogger_query = \
            """SELECT ORDERID, EVTTYPE FROM AUDITLOG WHERE EVTTYPE IN {} 
and BUSINESSPERIOD = {}""".format(self.eventtype, self.businessperiod)
        self.update_query_auditlogger = """UPDATE AUDITLOG SET EVTDATA = '{}' WHERE ORDERID = {} and EVTTYPE = '{}'"""

        self.is_backup = False
        self.auditlog_file = self.file_per_businessperiod()
        self.orders_to_validate = self.orders_on_auditlogger()
        self.boh_file = self.validate_boh_file()


    def make_backup(self):
        if not self.is_backup:
            print("Creating backup files ...\n")
            date_backupfile = datetime.now().strftime('%Y%m%d_%H%M%S')
            self.backup_auditlog = 'auditlog_' + date_backupfile
            self.backup_boh = 'bohpump_' + date_backupfile

            copyfile(self.auditlog_file, gettempdir() + os.path.join(os.sep, self.backup_auditlog))
            copyfile(self.boh_file, gettempdir() + os.path.join(os.sep, self.backup_boh))

            self.is_backup = True


    def validate_boh_file(self):
        boh_directory = os.path.join("bohpump")
        boh_full_path = os.path.join(self.server_directory, boh_directory)
        boh_file_name = "bohpump.ctrl"
        boh_file = os.path.join(self.application_path, boh_full_path, boh_file_name)
        if os.path.isfile(boh_file):
            return boh_file

        raise Exception("No file found: {}".format(boh_file))


    def file_per_businessperiod(self):
        templatelog = "mwapp_audit_{}.log".format(self.businessperiod)
        auditlog_directory = os.path.join("auditlog")
        auditlog_fullpath = os.path.join(self.server_directory, auditlog_directory)
        auditlog_file = os.path.join(self.application_path, auditlog_fullpath, templatelog)
        if os.path.isfile(auditlog_file):
            return auditlog_file

        raise Exception("No file found: {}".format(auditlog_file))


    def orders_on_auditlogger(self):
        print("Verifying orders...")
        orders_to_process = []
        try:
            connection, cursor = self.connection_database(self.auditlog_file)
            for orderid, state in cursor.execute(self.auditlogger_query):
                orders_to_process.append([orderid, state])
        except Exception as e:
            print("Unable to execute query on {}\n {}".format(self.auditlog_file, e))
        finally:
            cursor.close()

        if orders_to_process:
            return orders_to_process

        print("No orders found on: \n{} \nfor businessperiod: {}".format(self.auditlog_file, self.businessperiod))
        sys.exit(1)


    def update_auditlog(self, evt_data, orderid, evt_type):
        update = self.update_query_auditlogger.format(evt_data, orderid, evt_type)
        try:
            connection = sqlite3.connect(self.auditlog_file)
            cursor = connection.cursor()
            cursor.execute(update)
            connection.commit()
            cursor.close()
        except Exception as ex:
            raise ("Unable to update auditlog_file: {}".format(ex))


    def encode_data(self, evt_data):
        encoded = self.compress_data(evt_data)
        encode_data = b64encode(str(encoded))

        return encode_data


    def compress_data(self, evt_data):
        compress_obj = compressobj(Z_DEFAULT_COMPRESSION, DEFLATED, MAX_WBITS + 16)

        return compress_obj.compress(evt_data) + compress_obj.flush()


    def update_bohpump_database(self):
        connection, cursor = self.connection_database(self.boh_file)
        try:
            cursor.execute(self.update_query_boh)
            connection.commit()
            cursor.close()
        except Exception as ex:
            raise Exception("Unable to execute query: \n {}\n{}".format(self.boh_file, ex))


    def connection_database(self, database):
        connection = sqlite3.connect(database)
        cursor = connection.cursor()

        return connection, cursor


    def parse_orderpicture(self, orderpicture):
        order_element = ET.fromstring(orderpicture)
        parsed_orderpicture = ET.tostring(order_element.find('./'))

        return parsed_orderpicture


    def validate_totals(self, orderpicture, orderid):
        order_element = ET.fromstring(orderpicture)

        stateid = order_element.get("stateId")
        total_gross = float(order_element.get("totalGross"))
        total_tender = float(order_element.get("totalTender"))
        discount_amount = float(order_element.get("orderDiscountAmount"))
        change = float(order_element.get("change"))

        totals = total_tender - (total_gross + discount_amount + change)
        if totals != 0.0 and stateid == "5":
            self.orders_not_processed.append(orderid)
            return False

        return True


    def finalization(self):
        backup = "Backup Files saved on: \n{}\n{}\n"
        orders_updated = "Orders updated on mwapp_audit file:\n{}\n"
        restart = "Restart bohpump to send updated orders"
        not_found = "Orders not found on orders databases:\n{}\n"
        not_processed = "Orders not processed with inconsistent orderpicture:\n{}"
        done = "Process done.\n"

        if self.is_backup:
            self.update_bohpump_database()
            print(backup.format(os.path.join(gettempdir(), self.backup_auditlog),
                                os.path.join(gettempdir(), self.backup_boh)))
            print(orders_updated.format(','.join(str(o) for o in self.orders_to_proccess)))

            print(restart)

        print(not_found.format(','.join(str(o) for o in self.orders_not_found)) if self.orders_not_found else '')

        print(not_processed.format(','.join(str(o) for o in self.orders_not_processed)) if self.orders_not_processed else '')

        print(done)


def open_mwpos_connection(info_env, orderid):
    mbcontext = None
    bin_path = os.path.join(info_env.application_path, 'bin')
    common_path = os.path.join("src", "tablemgr", "src", "common")
    os.environ["BINPATH"] = os.path.join(info_env.application_path, bin_path)
    os.environ["HVPORT"] = "14000"
    os.environ["HVIP"] = "127.0.0.1"
    os.environ["HVCOMPPORT"] = "35689"
    os.environ["HVPID"] = "-1"
    sys.path.insert(0, os.path.join(info_env.application_path, common_path))

    try:
        os.chdir(os.environ["BINPATH"])

        from msgbus import MBEasyContext, FM_PARAM, TK_SLCTRL_OMGR_ORDERPICT, TK_SYS_ACK

        mbcontext = MBEasyContext("orderpicture_auditlogger")

        pos_id = "1"
        template_orderid = '\00{}'.format(orderid)
        msg = mbcontext.MB_EasySendMessage(
            "ORDERMGR{}".format(pos_id),
            TK_SLCTRL_OMGR_ORDERPICT,
            format=FM_PARAM,
            data=template_orderid
        )

        if msg.token == TK_SYS_ACK:
            parsed_data = msg.data.split("\0")
            order_picture = parsed_data[2]

        if order_picture:
            return info_env.parse_orderpicture(order_picture)

    except BaseException:
        raise Exception("Please execute with python on application directory")
    finally:
        if mbcontext:
            mbcontext.MB_EasyFinalize()


def application_and_file_path(dev_path=None):
    if dev_path:
        return dev_path

    platform = os.sys.platform
    linux_directory = "/home/administrador/edeployPOS"
    windows_directory = "C:\\edeployPOS"
    if platform == "linux2" and os.path.exists(linux_directory):
        return linux_directory
    elif platform == 'win32' and os.path.exists(windows_directory):
        return windows_directory


if __name__ == "__main__":
    businessperiod = raw_input("Inform a valid businessperiod: ")
    application_directory = \
        raw_input("Press enter to run on PROD Or inform a DEV Path: EX: C:\Dev\edeploy-pos-sctructure\n")

    if not application_directory:
        application_directory = application_and_file_path()

    executor = StandaloneExecution(application_directory, businessperiod)

    for order in executor.orders_to_validate:
        orderid, evt_type = order[0], order[1]
        orderpicture = open_mwpos_connection(executor, orderid)
        if orderpicture and executor.validate_totals(orderpicture, orderid):
            executor.make_backup()
            executor.orders_to_proccess.append(orderid)
            evt_data = executor.encode_data(orderpicture)
            executor.update_auditlog(evt_data, orderid, evt_type)
        elif orderpicture is None:
            executor.orders_not_found.append(orderid)

    executor.finalization()
