import base64
import logging
import time
from datetime import datetime
from threading import Thread
from xml.etree import cElementTree as eTree

import msgbus
import persistence
from persistence import Driver
from typing import List, Tuple, Optional, Union
from sysactions import get_model, get_posot
from old_helper import OrderTaker

from services import OrderService

logger = logging.getLogger("FiscalWrapper")


class FiscalDataRepository:
    def __init__(self, mbcontext):
        # type: (msgbus.MBEasyContext) -> FiscalDataRepository
        self.mbcontext = mbcontext
        self.conn = None  # type: Optional[persistence.Connection]
        self.order_service = OrderService()

    def __enter__(self):
        self.conn = Driver().open(self.mbcontext, service_name="FiscalPersistence")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.conn:
            self.conn.close()

    def start_save_fiscal_data_thread(self, pos_id, order, xml_request, numero_sat, sent_to_nfce, xml_response):
        self.set_fiscal_xml_custom_property(pos_id, xml_request, order.get("orderId", ""))
        save_fiscal_data_thread = Thread(target=self.save_fiscal_data(pos_id, order, xml_request, numero_sat, sent_to_nfce, xml_response))
        save_fiscal_data_thread.daemon = True
        save_fiscal_data_thread.start()

    def set_fiscal_xml_custom_property(self, pos_id, xml_request, order_id='', key='FISCAL_XML'):
        # type: (str, str, str, str) -> None

        self.order_service.set_custom_property(
            pos_id=pos_id,
            key=key,
            value=xml_request,
            order_id=order_id,
            blk_notify=True,
        )

    def notify_pump(
            self,
            pos_id='',                      # type: str
            order_id='',                    # type: str
            xml_format='CancelledOrder',    # type: str
            order_picture=None              # type: Union[None, str]
    ):
        # type: (...) -> None

        try:
            event_type = "ORDER_REPROCESS"
            if not order_picture:
                order_picture = OrderTaker().get_order_picture(pos_id, order_id)

            xml = "<{}>{}</{}>".format(xml_format, order_picture, xml_format)
            self.mbcontext.MB_EasyEvtSend(event_type, event_type, xml)
        except Exception as _:
            logger.exception("Error notifying bohbump on POS ID: {} for ORDER ID: {}".format(pos_id, order_id))

    def save_fiscal_data(self, posid, order, xml_request, numero_sat, sent_to_nfce, xml_response):
        retries = 0
        while retries < 3:
            try:
                xml = base64.b64decode(xml_request)
                parsed_xml = eTree.XML(xml)

                from nfce import NfceRequestBuilder
                numero_nota_element = parsed_xml.find("infCFe/ide/nCFe")
                if numero_nota_element is None:
                    # TODO: Obter o numero da nota via parametro e remover esta logica daqui
                    numero_nota_element = parsed_xml.find(
                        "{{{0}}}NFe/{{{0}}}infNFe/{{{0}}}ide/{{{0}}}nNF".format(
                            NfceRequestBuilder.NAMESPACE_NFE))
                    if numero_nota_element is None:
                        numero_nota_element = parsed_xml.find(
                            "{{{0}}}infNFe/{{{0}}}ide/{{{0}}}nNF".format(
                                NfceRequestBuilder.NAMESPACE_NFE))

                numero_nota = numero_nota_element.text

                data_nota_element = parsed_xml.find("infCFe/ide/dEmi")
                hora_nota_element = parsed_xml.find("infCFe/ide/hEmi")
                if data_nota_element is None:
                    dh_elment = parsed_xml.find(
                        "{{{0}}}NFe/{{{0}}}infNFe/{{{0}}}ide/{{{0}}}dhEmi".format(
                            NfceRequestBuilder.NAMESPACE_NFE))
                    if dh_elment is None:
                        dh_elment = parsed_xml.find(
                            "{{{0}}}infNFe/{{{0}}}ide/{{{0}}}dhEmi".format(
                                NfceRequestBuilder.NAMESPACE_NFE))

                    data_nota_str = dh_elment.text

                    data_emissao_date_utc = data_nota_str[:19]
                    data_nota_date = datetime.strptime(data_emissao_date_utc, "%Y-%m-%dT%H:%M:%S")
                else:
                    data_nota_str = data_nota_element.text
                    hora_nota_str = hora_nota_element.text
                    data_nota_date = datetime.strptime(data_nota_str + hora_nota_str, "%Y%m%d%H%M%S")

                data_nota_seconds = int(time.mktime(data_nota_date.timetuple()))

                params_fiscal = {
                    "pos_id": posid,
                    "order_id": order.get("orderId"),
                    "data_nota": data_nota_seconds,
                    "numero_nota": numero_nota,
                    "numero_sat": numero_sat,
                    "xml_request": xml_request,
                    "sent_to_nfce": sent_to_nfce,
                    "xml_response": xml_response
                }

                self.conn.pquery("fiscal_saveFiscalData", **params_fiscal)
                break
            except Exception as _:
                logger.exception("Error saving fiscal data on database. RetryNum: {}".format(retries))
                time.sleep(0.2)
                retries += 1
        else:
            logger.error(">>> Save fiscal data failed after maximum retries!")

    def set_order_picture(self, order_id, order_picture):
        # type: (str, str) -> None
        params = {
            "order_id": order_id,
            "order_picture": order_picture
        }
        self.conn.pquery("fiscal_setOrderPicture", **params)

    def set_order_canceled(self, order_id):
        # type: (str) -> None
        params = {
            "order_id": order_id,
        }
        self.conn.pquery("fiscal_setOrderCanceled", **params)

    def get_nfce_orders_to_send(self, limit):
        # type: (int) -> List
        params_fiscal = {
            "limit": limit
        }
        orders = [Order(x.get_entry(0), x.get_entry(1), x.get_entry(2), x.get_entry(3), x.get_entry(4)) for
                  x in self.conn.pselect("fiscal_getNfceToSend", **params_fiscal)]
        return orders

    def get_count_nfce_orders_to_send(self):
        # type: () -> int
        return [int(x.get_entry(0)) for x in self.conn.pselect("fiscal_getNfceToSendCount")][0]

    def set_nfce_sent(self, order_id, status):
        # type: (str, int) -> None
        params_fiscal = {
            "status": status,
            "order_id": order_id
        }
        self.conn.pquery("fiscal_setNfceSent", **params_fiscal)

    def set_xmlresponse_contingency(self, order_id, xml_base64):
        # type: (str, str) -> None
        params_fiscal = {
            "order_id": order_id,
            "xml_response": xml_base64
        }
        self.conn.pquery("fiscal_setXmlResponse", **params_fiscal)

    def set_nfce_sent_with_xml(self, order_id, xml_base64, status):
        # type: (str, str, int) -> None
        params_fiscal = {
            "status": status,
            "xml_request": xml_base64,
            "order_id": order_id
        }
        self.conn.pquery("fiscal_setNfceSentXml", **params_fiscal)

    def set_nfce_sent_with_xml_bkoffice(self, order_id, xml_base64, status, bkoffice_status):
        # type: (str, str, int, int) -> None
        params_fiscal = {
            "status": status,
            "xml_request": xml_base64,
            "order_id": order_id,
            "sent_to_bkoffice": bkoffice_status
        }
        self.conn.pquery("fiscal_setNfceSentXmlBKOffice", **params_fiscal)

    def retry_fiscal_orders_with_exception(self, range_of_days):
        # type: (str) -> None
        params_fiscal = {
            "range_of_days": "-{} days".format(range_of_days),
        }
        self.conn.pquery("fiscal_retryFiscalOrdersWithException", **params_fiscal)

    def get_xml_request(self, order_id):
        # type: (str) -> str
        params_fiscal = {
            "order_id": order_id
        }
        cursor = self.conn.pselect("fiscal_getRequestXMLFiscalData", **params_fiscal)
        if cursor.rows() > 0:
            ret = [x.get_entry(0) for x in cursor][0]
            return ret

    def get_xml_response(self, order_id):
        # type: (str) -> str
        params_fiscal = {
            "order_id": order_id
        }
        cursor = self.conn.pselect("fiscal_getResponseXMLFiscalData", **params_fiscal)
        if cursor.rows() > 0:
            ret = [x.get_entry(0) for x in cursor][0]
            return ret

    def get_numero_sat_and_xml_request(self, order_id):
        # type: (str) -> Tuple
        params_fiscal = {
            "order_id": order_id
        }
        cursor = self.conn.pselect("fiscal_getSatNumberAndRequestXMLFiscalData", **params_fiscal)
        if cursor.rows() > 0:
            ret = [(x.get_entry(0), x.get_entry(1)) for x in cursor][0]
            return ret


class Order:
    def __init__(self, order_id, pos_id, xml, num_nota, order_picture):
        self.order_id = order_id
        self.pos_id = pos_id
        self.xml = xml
        self.num_nota = num_nota
        self.order_picture = order_picture


class Tender(object):
    def __init__(self):
        # type: () -> Tender
        self.tender_seq_id = ""
        self.type = ""
        self.amount = ""
        self.change = ""
        self.bandeira = ""
        self.auth_code = ""
        self.cnpj_auth = ""
        self.receipt_customer = ""
        self.receipt_merchant = ""
