from xml.etree import cElementTree as eTree

from modelcommon.fiscalwrapper import (
    FiscalizationErrorsCode,
    FiscalizationException,
)
from mw_helper import format_currency

from typing import Dict, Tuple, Optional, List


class FiscalParameterNotFound(Exception):
    def __init__(self, part_code, parameter_name):
        # type: (str, unicode) -> None

        super(FiscalParameterNotFound, self).__init__("FiscalParameterNotFound")
        self.part_code = str(part_code)
        self.parameter_name = parameter_name

    def __str__(self):
        return "FiscalParameterNotFound: partCode: {0} - parameterName: {1}".format(self.part_code, self.parameter_name)


class FiscalParameterController(object):
    def __init__(
        self,
        fiscal_parameters,          # type: Dict[Tuple[str, unicode],unicode]
        tip_config_range,           # type: Dict
        loader_fiscal_parameters,   # type: List[Dict]
        fiscal_max_value,           # type: float
    ):
        # type: (...) -> None

        self.fiscal_parameters = fiscal_parameters
        self.tip_config_range = tip_config_range
        self.loader_fiscal_parameters = loader_fiscal_parameters
        self.fiscal_max_value = fiscal_max_value

    def get_optional_parameter(
            self,
            part_code,      # type: str
            parameter_name  # type: unicode
    ):
        # type: (...) -> Optional[unicode]

        key = (str(part_code), parameter_name)
        if key not in self.fiscal_parameters:
            return None
        else:
            return self.fiscal_parameters[key] or None

    def get_parameter(
            self,
            part_code,      # type: str
            parameter_name  # type: unicode
    ):
        # type: (...) -> unicode

        key = (str(part_code), parameter_name)
        if key not in self.fiscal_parameters:
            raise FiscalParameterNotFound(part_code, parameter_name)
        else:
            return self.fiscal_parameters[key] or None

    def has_parameter(
            self,
            part_code,      # type: str
            parameter_name  # type: unicode
    ):
        # type: (...) -> bool

        key = (str(part_code), parameter_name)
        return key in self.fiscal_parameters

    def get_all_products_csts(self):
        # type: () -> unicode

        return str({(x, y): self.fiscal_parameters.get((x, y)) for x, y in self.fiscal_parameters if "CST" in y})

    def get_all_products_base_reductions(self):
        # type: () -> unicode

        return str({x: self.fiscal_parameters.get((x, "BASE_REDUCTION")) for x, y in self.fiscal_parameters})

    def get_tip_params(self, part_code=None, taxes_name=None):
        # type: (str, List) -> []

        products = []
        try:
            if len(self.tip_config_range) == 0:
                return products
            for item in self.tip_config_range:
                if item["exempt"] or (part_code is not None and part_code != item["TipCode"]):
                    continue
                products.extend(self._get_taxes(item, taxes_name))
        except Exception as _:
            pass
        return products

    def _get_taxes(self, item, taxes_name=None):
        # type:(Dict, []) -> []

        taxes = ['PIS', 'COFINS', 'ICMS'] if taxes_name is None else taxes_name
        products = []
        for product in self.loader_fiscal_parameters:
            product_code = str(product['PRODUCT_CODE'])
            tip_code = str(item["TipCode"])
            if product_code != tip_code:
                continue
            for param in product:
                if param not in taxes:
                    continue
                rate = float(product[param])
                code = "{}_{}".format(str(param), rate)
                name = str(param)
                product_code = {tip_code: tip_code}
                index = str(param[:1])
                result = {"fiscalIndex": index, "code": code, "rate": rate, "name": name, "product": product_code}

                products.append(result)

        return products

    def validate_fiscal_max_value(
        self,   # type: FiscalParameterController
        model,  # type: eTree.XML
        order,  # type: eTree.XML
    ):
        # type: (...) -> Optional[str]

        try:
            self.can_fiscalize_order(order=order)
        except FiscalizationException as ex:
            formatted_value = format_currency(
                model=model,
                value=float(ex.message),
            )
            return "$MAXIMUM_AMOUNT_TO_FISCALIZE|{0}|{0}".format(formatted_value)

    def can_fiscalize_order(
        self,   # type: FiscalParameterController
        order,  # type: eTree.XML
    ):
        # type: (...) -> None

        order_value = float(order.get("totalGross") or "0.0") + float(order.get("tip") or "0.0")

        if order_value > self.fiscal_max_value:
            raise FiscalizationException(
                error_code=FiscalizationErrorsCode.FiscalMaxValueExceeded.value,
                message=str(self.fiscal_max_value)
            )
