# -*- coding: utf-8 -*-
import base64
import gzip
import json
import threading
import requests
from cStringIO import StringIO
from xml.etree import cElementTree as eTree

from application.configurator import Configurator
from application.eventsender import EventSender
from application.interactor import OrderPictureInteractor, OperatorLogoutInteractor, ProductionOrderInteractor, \
    TablePictureInteractor
from application.model import EventData, AuditEvent, Command
from ._AuditEventConsumer import AuditEventConsumer
from syserrors import SE_SUCCESS, SE_BADXML, SE_BADRESPONSE
from typing import Optional


class AuditEventConsumerEventHandler(AuditEventConsumer):

    def __init__(self, configurations):
        # type: (Configurator) -> None

        self.configs = configurations
        self.repository = None
        self.event_sender = EventSender(self.configs)

        self.operator_logout_interactor = None
        self.order_picture_interactor = None
        self.table_picture_interactor = None
        self.production_order_interactor = None

        super(self.__class__, self).__init__(self.configs, self.process)

        with self.lock:
            self._create_commands_table()

        t = threading.Thread(target=self._status_sender)
        t.daemon = True
        t.start()

    def set_repository(self, repository):
        self.repository = repository

    def build_interactors(self):
        # type: () -> None

        self.operator_logout_interactor = OperatorLogoutInteractor(self.configs, self.repository)
        self.order_picture_interactor = OrderPictureInteractor(self.configs, self.repository)
        self.table_picture_interactor = TablePictureInteractor(self.configs)
        self.production_order_interactor = ProductionOrderInteractor(self.configs)

    def process(self, evt_list):
        for evt in evt_list:
            try:
                data, evt_type = evt[0], evt[1]
                self.logger.info("New event received: [{}]".format(evt_type))
                event_data = self._get_event_data(evt_type, data)
            except Exception as _:
                message = "Auditlog event parser error. Exiting to force resynchronization. Event: {}".format(evt)
                self.logger.exception(message)
                return SE_BADXML

            try:
                if evt_type in [AuditEvent.PAID, AuditEvent.VOID_ORDER, AuditEvent.ORDER_REPROCESS]:
                    payload = self.order_picture_interactor.build(event_data)
                    result = self.event_sender.send_request(event_data, payload)
                    if result != SE_SUCCESS:
                        return result

                elif evt_type in [AuditEvent.TABLE_CLOSED, ]:
                    payload = self.table_picture_interactor.build(event_data)
                    result = self.event_sender.send_request(event_data, payload, True)
                    if result != SE_SUCCESS:
                        return result

                elif evt_type == AuditEvent.OPERATOR_LOGOUT:
                    payload = self.operator_logout_interactor.build(event_data)
                    if not payload:
                        continue
                    result = self.event_sender.send_request(event_data, payload)
                    if result != SE_SUCCESS:
                        return result

                elif evt_type == AuditEvent.PRODUCTION_ORDER_TIMES:
                    payload = self.production_order_interactor.build(event_data)
                    result = self.event_sender.send_request(event_data, payload)
                    if result != SE_SUCCESS:
                        return result

            except Exception as _:
                self.logger.exception("[{}] Unable to process event".format(evt_type))
                return SE_BADRESPONSE

        return SE_SUCCESS

    def _status_sender(self):
        ctrl_conn = self._create_db_conn()

        while not self.terminating:
            wait_time = 15
            try:
                command = None
                if self.repository is not None:
                    order_picture = self.repository.get_last_order_picture()
                    last_command_id = self._get_last_command_id(ctrl_conn)
                    command = self._send_status(order_picture, last_command_id)
                    wait_time = 600

                if command and command.type == "ReprocessSales":
                    self.logger.info("Received ReprocessSales Command")
                    self._update_command_id(command.id, ctrl_conn)
                    self.force_new_period(command.data, ctrl_conn)

            except WindowsError as _:
                self.renew_service()
                self.logger.exception("On WindowsError")
            except Exception as _:
                wait_time = 60
                self.logger.exception("ERROR on PUMP Status Thread")

            finally:
                with self.condition:
                    self.condition.wait(wait_time)

    def _create_commands_table(self, ctrl_conn=None):
        if not ctrl_conn:
            ctrl_conn = self.ctrl_conn

        ctrl_conn.execute("""
                CREATE TABLE IF NOT EXISTS CommandsProcessed (
                    CommandId   INTEGER NOT NULL,
                    PRIMARY KEY (CommandId)
                );
            """)

    def _update_command_id(self, command_id, ctrl_conn=None):
        if not ctrl_conn:
            ctrl_conn = self.ctrl_conn

        query = "INSERT OR IGNORE INTO CommandsProcessed(CommandId) VALUES(:command_id);"
        ctrl_conn.execute(query, {"command_id": command_id})

    def _get_last_command_id(self, ctrl_conn=None):
        if not ctrl_conn:
            ctrl_conn = self.ctrl_conn

        c = ctrl_conn.cursor()
        r = c.execute("""SELECT MAX(CommandId) FROM CommandsProcessed;""").fetchone()
        return r[0]

    def _send_status(self, order_picture, last_command_id):
        # type: (str, str) -> Optional[Command]

        xml_picture = eTree.fromstring(order_picture)
        order = xml_picture.find(".//Order")
        if order is None or order.get("orderId") is None:
            raise Exception()

        message = {
            "lastOrderId": order.get("orderId"),
            "lastOrderDatetime": order.get("createdAtGMT") + "Z",
            "lastCommandIdProcessed": last_command_id
        }

        payload = json.dumps(message)
        try:
            payload = payload.encode("utf-8")
            self.logger.info("Sending Pump Status to server. Payload:\n{}".format(payload))

            headers = {
                "Accept": "application/json",
                "Content-type": "application/json",
                "x-api-key": self.configs.api_key
            }

            post_url = self.configs.server_url + self.configs.pump_status_url + "/{}".format(self.configs.store_id)
            response = requests.post(post_url, headers=headers, data=payload, timeout=30, verify=False)  # NOSONAR

            if response.status_code != 200:
                log_message = "Error sending Pump Status server. Status: {} - {}"
                self.logger.info(log_message.format(response.status_code, response.content))
                return None

            self.logger.info("Success sending Pump Status to server")

            if response.content == "":
                return None
            return Command(response.json())

        except Exception as _:
            message = "Communication error sendingPump Status to server"
            self.logger.exception(message)
            return None

    @staticmethod
    def _get_event_data(evt_type, xml):
        # type: (str, str) -> EventData

        event = eTree.XML(xml)
        audit_log_evt = event.find("AuditLog")
        evt_timestamp = audit_log_evt.get("evtTime") or ""
        evt_data = audit_log_evt.text.encode("UTF-8")
        data = eTree.fromstring(gzip.GzipFile(fileobj=StringIO(base64.b64decode(evt_data))).read())
        session_id = data.get("sessionid")

        return EventData(data=data, event_type=evt_type, timestamp=evt_timestamp, session_id=session_id)
