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

import requests
from syserrors import SE_SUCCESS, SE_BADXML, SE_BADRESPONSE
from typing import Optional

from application.configurator import Configurator
from application.eventsender import EventSender
from application.interactor import (
    OrderPictureInteractor,
    OperatorLogoutInteractor,
    ProductionOrderInteractor,
    TablePictureInteractor,
    StoreStatusInteractor,
)
from application.model import EventData, AuditEvent, Command
from ._AuditEventConsumer import AuditEventConsumer


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.table_picture_interactor = None
        self.production_order_interactor = None
        self.store_status_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.table_picture_interactor = TablePictureInteractor(self.configs)
        self.production_order_interactor = ProductionOrderInteractor(self.configs)
        self.store_status_interactor = StoreStatusInteractor(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:
                events_to_generate_order_picture = [
                    AuditEvent.PAID,
                    AuditEvent.VOID_ORDER,
                    AuditEvent.ORDER_REPROCESS,
                    AuditEvent.CLEAR_TENDERS
                ]
                if evt_type in events_to_generate_order_picture:
                    order_picture_interactor = OrderPictureInteractor(self.configs, self.repository)
                    payload = 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:
                    payload = self.production_order_interactor.build(event_data)
                    result = self.event_sender.send_request(event_data, payload)
                    if result != SE_SUCCESS:
                        return result

                elif evt_type in [AuditEvent.CLOSE_STORE_DAY, AuditEvent.OPEN_STORE_DAY]:
                    payload = self.store_status_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()
                    if order_picture is not None:
                        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)
