import json
import logging
import re

from application.model import ApplierSubTypes
from application.repository.api import APIRepository
from order_api import OrderService
from order_api.dto import CustomProperty
from table_manager_api import TableService
from table_manager_api.dto import (
    TableServiceDto,
    TableStatus,
)
from typing import (
    Dict,
    List,
    Optional,
    Union,
)

from .dto import GetLoyaltyCustomerInfoRequest

logger = logging.getLogger(__name__)


class GetLoyaltyCustomerInfoInteractor(object):

    def __init__(self, api_repository):
        # type: (APIRepository) -> None

        self.api_repository = api_repository

    def get_loyalty_customer_info(
        self,       # type: GetLoyaltyCustomerInfoInteractor
        request,    # type: GetLoyaltyCustomerInfoRequest
    ):
        # type: (...) -> Optional[str]

        logger.info("Requesting customer loyaltySystemId")
        loyalty_info = self.api_repository.retrieve_loyalty_customer_info(loyalty_customer_id=request.loyalty_id)
        logger.info("Retrieved customer loyalty data: {}".format(loyalty_info))
        loyalty_info = self._update_applier_type(customer_info=loyalty_info)
        if loyalty_info:
            self._save_loyalty_info(
                loyalty_info=loyalty_info,
                request=request,
            )
        return json.dumps(loyalty_info)

    @staticmethod
    def _update_applier_type(
        customer_info,  # type: str
    ):
        # type: (...) -> Dict[str, Union[str, Dict, List]]

        if not customer_info:
            return customer_info

        try:
            info = json.loads(customer_info)
            benefits = info.get("benefits", [])
            for benefit in benefits:
                benefit["applier"] = "Loyalty"
                benefit["applierSubType"] = ApplierSubTypes.LOYALTY

            return info
        except ValueError:
            logger.error("Invalid JSON returned: {}".format(customer_info))
            return {}

    def _save_loyalty_info(
        self,           # type: GetLoyaltyCustomerInfoInteractor
        loyalty_info,   # type: Dict[str, Union[str, Dict, List]]
        request,        # type: GetLoyaltyCustomerInfoRequest
    ):
        # type: (...) -> None

        order_service = OrderService(
            pos_id=request.pos_id,
            message_bus=self.api_repository.message_bus,
        )
        table_service = TableService(
            pos_id=request.pos_id,
            message_bus=self.api_repository.message_bus,
        )
        loyalty_properties = self._parse_loyalty_customer_info(
            loyalty_customer_info=loyalty_info,
            request=request,
        )
        if request.table_id:
            return self._save_table_loyalty_info(
                table_service=table_service,
                table_id=request.table_id,
                seat=request.seat,
                loyalty_properties=loyalty_properties,
                order_service=order_service,
            )

        return self._save_order_loyalty_info(
            order_id=request.order_id,
            loyalty_properties=loyalty_properties,
            order_service=order_service,
        )

    def _save_table_loyalty_info(
        self,                   # type: GetLoyaltyCustomerInfoInteractor
        table_service,          # type: TableService
        table_id,               # type: str
        seat,                   # type: str
        loyalty_properties,     # type: Dict[str, Union[str, List, Dict]]
        order_service,          # type: OrderService
    ):
        # type: (...) -> None

        table_service_dto = table_service.get_service(table_id=table_id)
        if table_service_dto.status == TableStatus.Totaled:
            self._save_order_loyalty_info(
                order_id=table_service_dto.payment_order.order_id,
                loyalty_properties=loyalty_properties,
                order_service=order_service,
            )

        table_loyalty_properties = self._format_to_table_loyalty_properties(
            table_service_dto=table_service_dto,
            loyalty_properties=loyalty_properties,
            seat=seat,
        )

        table_service.set_custom_properties(
            properties=table_loyalty_properties,
            table_id=table_id,
        )

    def _format_to_table_loyalty_properties(
        self,                   # type: GetLoyaltyCustomerInfoInteractor
        table_service_dto,      # type: TableServiceDto
        loyalty_properties,     # type: Dict[str, Union[str, List, Dict]]
        seat,                   # type: str
    ):
        # type: (...) -> Dict[str, str]

        table_loyalty_properties = dict(loyalty_properties)

        original_table_loyalty_properties = self._get_table_loyalty_properties(table_service_dto=table_service_dto)
        for key, value in table_loyalty_properties.items():
            if key in ["CUSTOMER_NAME", "CUSTOMER_DOC"]:
                if seat == "table":
                    original_table_loyalty_properties[key] = value
                continue

            if key in original_table_loyalty_properties:
                original_table_loyalty_properties[key].update({seat: value})
            else:
                original_table_loyalty_properties[key] = {seat: value}

            original_table_loyalty_properties[key] = json.dumps(original_table_loyalty_properties[key])

        return original_table_loyalty_properties

    @staticmethod
    def _get_table_loyalty_properties(
        table_service_dto,  # type: TableServiceDto
    ):
        # type: (...) -> Dict[str, Dict]

        return {
            key: json.loads(value)
            for key, value in table_service_dto.custom_properties.items()
            if key in [
                "LOYALTY_CUSTOMER_INFO",
                "LOYALTY_CUSTOMER_BENEFITS",
                "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL",
                "LOYALTY_ID",
            ]
        }

    @staticmethod
    def _save_order_loyalty_info(
        order_id,               # type: int
        loyalty_properties,     # type: Dict[str, Union[str, List, Dict]]
        order_service,          # type: OrderService
    ):
        # type: (...) -> None

        json_fields = ["LOYALTY_CUSTOMER_INFO", "LOYALTY_CUSTOMER_BENEFITS", "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"]
        order_properties = [
            CustomProperty(
                key=key,
                value=json.dumps(value) if key in json_fields else value,
            ) for key, value in loyalty_properties.items()
        ]

        order_service.set_order_custom_properties(
            custom_properties=order_properties,
            order_id=order_id,
        )

    def _parse_loyalty_customer_info(
        self,                   # type: GetLoyaltyCustomerInfoInteractor
        loyalty_customer_info,  # type: Dict[str, Union[str, Dict, List]]
        request,                # type: GetLoyaltyCustomerInfoRequest
    ):
        # type: (...) -> Dict[str, Union[str, Dict, List]]

        customer_benefits = loyalty_customer_info.pop("benefits", [])
        customer = loyalty_customer_info.get("customerInfo", {}).get("customer", {})
        customer_document = customer.get("document") or ""
        customer["isValidDocument"] = self._validate_document(customer_doc=customer_document) is not False
        loyalty_customer_info.get("customerInfo", {}).update(customer)

        result = {
            "LOYALTY_ID": request.loyalty_id,
            "LOYALTY_CUSTOMER_INFO": loyalty_customer_info,
        }
        if customer.get("name"):
            result["CUSTOMER_NAME"] = customer["name"].encode("utf-8")
        if customer_document:
            result["CUSTOMER_DOC"] = customer_document

        if not customer_benefits:
            return result

        result["LOYALTY_CUSTOMER_BENEFITS"] = customer_benefits

        result["LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"] = [
            benefit.get("id") for benefit in customer_benefits
            if benefit.get("automatic") == "ON_TOTAL"
        ]
        return result

    def _validate_document(
        self,           # type: GetLoyaltyCustomerInfoInteractor
        customer_doc,   # type: str
    ):
        # type: (...) -> bool

        if len(customer_doc) <= 11:
            return self.validate_cpf(cpf=customer_doc)

        return self.validate_cnpj(cnpj=customer_doc)

    @staticmethod
    def validate_cpf(
        cpf,    # type: str
    ):
        # type: (...) -> Union[bool, str]

        cpf = "".join(re.findall(r"\d", str(cpf)))
        if not cpf or len(cpf) < 11:
            return False

        integers = list(map(int, cpf))
        new = integers[:9]

        if all(num == integers[0] for num in integers):
            return False

        while len(new) < 11:
            r = sum([(len(new) + 1 - i) * v for i, v in enumerate(new)]) % 11

            if r > 1:
                f = 11 - r
            else:
                f = 0
            new.append(f)

        if new == integers:
            return cpf

        return False

    @staticmethod
    def validate_cnpj(
        cnpj,   # type: str
    ):
        # type: (...) -> Union[bool, str]

        cnpj = "".join(re.findall(r"\d", str(cnpj)))
        if not cnpj or len(cnpj) < 14:
            return False

        integers = list(map(int, cnpj))
        new = integers[:12]

        if all(num == integers[0] for num in integers):
            return False

        prod = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]
        while len(new) < 14:
            r = sum([x * y for (x, y) in zip(new, prod)]) % 11
            if r > 1:
                f = 11 - r
            else:
                f = 0
            new.append(f)
            prod.insert(0, 6)

        if new == integers:
            return cnpj

        return False
