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, tip_config_range, loader_fiscal_parameters):
        self.fiscal_parameters = fiscal_parameters  # type: Dict[Tuple[str, unicode],unicode]
        self.tip_config_range = tip_config_range  # type: Dict
        self.loader_fiscal_parameters = loader_fiscal_parameters  # type: Dict

    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
