import logging

from helper import remove_accents
from modelcommon.model import Store
from models import NfeIntermediary, NfePresence
from old_helper import validar_cnpj
from typing import Dict, Any, Optional
from datetime import datetime
from dateutil import tz
from xml.etree import cElementTree as eTree

from pos_model import Order
import sysactions

from _NfceXmlPartBuilder import NfceXmlPartBuilder
from _ContextKeys import ContextKeys
from comp_exceptions import FiscalGetIdException

logger = logging.getLogger("FiscalWrapper")


class NfceEmissionType(object):
    normal = 1
    contingency = 9


class IdeBuilder(NfceXmlPartBuilder):

    def __init__(
            self,
            serie,              # type: int
            mod,                # type: int
            ambiente,           # type: int
            versao_ws,          # type: int
            tenders_type,       # type: Dict
            store_model         # type: Store
    ):
        #  type: (...) -> None

        self.serie = serie
        self.mod = mod
        self.ambiente = ambiente
        self.versao_ws = versao_ws
        self.tenders_type = tenders_type
        self.store_model = store_model

    def build_xml(self, order, context):
        # type: (Order, Dict[unicode, Any]) -> unicode

        is_in_contingency = context[ContextKeys.is_in_contingency]
        contingency_datetime = context[ContextKeys.contingency_datetime]
        contingency_reason = context[ContextKeys.contingency_reason]

        last_totaled_state = None
        for order_state in reversed(order.states):
            if order_state.name == "TOTALED":
                last_totaled_state = order_state
                break

        if last_totaled_state is None:
            raise Exception("The order was not totaled")

        last_tender_time = last_totaled_state.timestamp
        if order.tenders and order.tenders[-1].time > last_tender_time:
            last_tender_time = order.tenders[-1].time

        data_emissao = self._format_date(last_tender_time)

        context[ContextKeys.data_emissao] = data_emissao
        serie_nota = int(self.serie)

        numero_nota = self._get_fiscal_id(order.pos_id, order.order_id)
        context[ContextKeys.fiscal_number] = numero_nota

        tp_emis = NfceEmissionType.contingency if is_in_contingency else NfceEmissionType.normal
        context[ContextKeys.emission_type] = tp_emis

        delivery_partner = self._get_delivery_partner(order)
        context[ContextKeys.delivery_partner] = delivery_partner

        no_intermediary = NfeIntermediary.no_intermediary
        ind_intermediary = no_intermediary if delivery_partner is None else NfeIntermediary.third
        ind_presence = NfePresence.presential if ind_intermediary == no_intermediary else NfePresence.delivery

        inner_xml = "<ide>" \
                    "<cUF>%s</cUF>" \
                    "<cNF>${CNF_RANDOM}</cNF>" \
                    "<natOp>venda</natOp>" % self.store_model.state_code
        if self.versao_ws in (1, 3):
            inner_xml += "<indPag>0</indPag>"
        inner_xml += "<mod>%s</mod>" \
                     "<serie>%s</serie>" \
                     "<nNF>%s</nNF>" \
                     "<dhEmi>%s</dhEmi>" \
                     "<tpNF>1</tpNF>" \
                     "<idDest>1</idDest>" \
                     "<cMunFG>%s</cMunFG>" \
                     "<tpImp>4</tpImp>" \
                     "<tpEmis>%d</tpEmis>" \
                     "<cDV>${CDV}</cDV>" \
                     "<tpAmb>%s</tpAmb>" \
                     "<finNFe>1</finNFe>" \
                     "<indFinal>1</indFinal>" \
                     "<indPres>%s</indPres>" \
                     "<indIntermed>%s</indIntermed>"\
                     "<procEmi>0</procEmi>" \
                     "<verProc>v1.00.00</verProc>" \
                     % (self.mod,
                        serie_nota,
                        numero_nota,
                        data_emissao,
                        self.store_model.city_code,
                        tp_emis,
                        self.ambiente,
                        ind_presence,
                        ind_intermediary)

        if is_in_contingency:
            tag_contingency = "<dhCont>{}</dhCont><xJust>{}</xJust>"
            inner_xml += tag_contingency.format(self._format_date(contingency_datetime), contingency_reason)

        inner_xml += "</ide>"

        return inner_xml

    @staticmethod
    def _get_fiscal_id(pos_id, order_id):
        # type: (int, int) -> int

        try:
            fiscal_id_property = "FISCAL_ID"

            model = sysactions.get_model(pos_id)
            pos_ot = sysactions.get_posot(model)

            xml = eTree.XML(pos_ot.getOrderCustomProperties(fiscal_id_property, orderid=str(order_id)))
            if xml:
                fiscal_id_node = xml.find('.//OrderProperty[@key="{}"]'.format(fiscal_id_property))
                if fiscal_id_node is not None:
                    fiscal_id = int(fiscal_id_node.get("value"))
                    return fiscal_id

        except Exception as _:
            logger.error("Error getting fiscal_id custom property from order [{}]".format(order_id))

        raise FiscalGetIdException()

    @staticmethod
    def _format_date(data):
        # type: (datetime) -> str

        local_zone = tz.tzlocal()
        data = data.replace(tzinfo=local_zone)
        data_str = data.strftime("%Y-%m-%dT%H:%M:%S%z")
        data_str = data_str[:22] + ":" + data_str[22:]

        return data_str

    def _get_delivery_partner(
            self,
            order   # type: Order
    ):
        # type: (...) -> Optional[Dict]

        order_picture = order.order_picture_xml
        delivery_partner = order_picture.find("CustomOrderProperties/OrderProperty[@key='PARTNER']")
        pickup_type = order_picture.find("CustomOrderProperties/OrderProperty[@key='PICKUP_TYPE']")
        is_delivery = True if pickup_type is not None and pickup_type.get("value").upper() == "DELIVERY" else False

        if delivery_partner is not None and is_delivery:
            partner = remove_accents(delivery_partner.get("value")).upper()
            if partner in self.tenders_type:
                delivery_address = self._get_delivery_address(order.order_id)
                if delivery_address is None:
                    return None

                partner_json = self.tenders_type[partner]
                cnpj = partner_json.get("cnpj")
                partner_id = partner_json.get("clientId", None)
                cnpj_is_valid = validar_cnpj(cnpj)
                json_data = dict({"partnerId": partner_id, "cnpj": cnpj, "deliveryAddress": delivery_address})
                partner_id_is_valid = True if partner_id and partner_id.strip() else False
                return json_data if cnpj_is_valid and partner_id_is_valid else None

        return None

    def _get_delivery_address(
            self,
            order_id,       # type: int
    ):
        # type: (...) -> Optional[dict]

        try:
            if self.store_model.city_code is None:
                logger.error("No has city code to store")
                return None

            return {
                "postalCode": self.store_model.postal_code,
                "streetName": self.store_model.address_street[:60],
                "complement": self.store_model.address_complement[:60],
                "streetNumber": self.store_model.address_number[:60],
                "neighborhood": self.store_model.neighborhood[:60],
                "city": self.store_model.city[:60],
                "state": self.store_model.state[:2],
                "municipalityCode": self.store_model.city_code,
            }
        except (Exception,):
            logger.error("Error getting delivery address from order [{}]".format(order_id))
            return None
