from requests.exceptions import RequestException

from ali_express_ds.ds.drop_shipping import getFreight, getProduct
from ali_express_ds.models import AliExpressProductSave
from ali_express_ds.pms.import_product import (
    AliExpressUpstreamError,
    _getProductSkus,
)
from ali_express_ds.pms.save_product import _getDeliveryOption, _getStock
from basic.models import Language
from shop.models import Currency, Seller


class SupplierDataError(Exception):
    """The SKU cannot be refreshed from its configured supplier."""


class AliExpressSupplier:
    product_key_prefix = "ali_express_"

    def retrieve(self, sku, currency_code=None):
        product_key = sku.productId.keyName or ""
        if not product_key.startswith(self.product_key_prefix):
            raise SupplierDataError(
                f"Product {sku.productId_id} has no supported supplier identifier."
            )

        supplier_product_id = product_key[len(self.product_key_prefix) :]
        product_save = self._get_product_save(supplier_product_id)
        currency = currency_code or self._currency_for(
            sku,
            product_save.currencyId,
        )
        language = self._language_for(product_save.languageId)
        product_json = getProduct(
            product_save.shipToCountry,
            supplier_product_id,
            currency,
            language,
            False,
        )
        supplier_skus = _getProductSkus(product_json)
        supplier_sku = self._match_sku(sku.serial, supplier_skus)
        if supplier_sku is None:
            raise SupplierDataError(
                f"Supplier response did not contain SKU {sku.pk}."
            )

        result = {
            "stock": _getStock(supplier_sku),
            "prices": [
                {
                    "currency": currency,
                    "salePrice": self._float_or_none(
                        supplier_sku.get("offer_sale_price")
                        or supplier_sku.get("offer_bulk_sale_price")
                        or supplier_sku.get("sku_price")
                    ),
                    "originalPrice": self._float_or_none(
                        supplier_sku.get("sku_price")
                    ),
                }
            ],
            "shippingOptions": [],
        }

        freight_json = getFreight(
            1,
            product_save.shipToCountry,
            supplier_product_id,
            supplier_sku["sku_id"],
            language,
            currency,
            language,
        )
        delivery = _getDeliveryOption(freight_json)
        if delivery:
            result["shippingOptions"].append(
                {
                    "currency": currency,
                    "isFreeShipping": self._bool(
                        delivery.get("free_shipping")
                    ),
                    "minDeliveryDays": self._int_or_none(
                        delivery.get("min_delivery_days")
                    ),
                    "maxDeliveryDays": self._int_or_none(
                        delivery.get("max_delivery_days")
                    ),
                    "cost": self._float_or_none(
                        delivery.get("shipping_fee_cent")
                    ),
                    "defaultCost": self._float_or_none(
                        delivery.get("default_shipping_fee")
                    ),
                }
            )
        return result

    @staticmethod
    def _get_product_save(supplier_product_id):
        product_save = (
            AliExpressProductSave.objects.filter(
                productId=supplier_product_id,
            )
            .only("shipToCountry", "currencyId", "languageId")
            .order_by("-id")
            .first()
        )
        if product_save is None:
            raise SupplierDataError(
                "No AliExpress save configuration was found for "
                f"product {supplier_product_id}."
            )
        return product_save

    @staticmethod
    def _currency_for(sku, currency_id):
        price = sku.sku_skuprice.select_related("currencyId").first()
        if price and price.currencyId.code:
            return price.currencyId.code

        currency = Currency.objects.filter(id=currency_id).only("code").first()
        if currency is None or not currency.code:
            raise SupplierDataError(
                f"Currency {currency_id} was not found or has no code."
            )
        return currency.code

    @staticmethod
    def _language_for(language_id):
        language = Language.objects.filter(id=language_id).only(
            "languageCode"
        ).first()
        if language is None or not language.languageCode:
            raise SupplierDataError(
                f"Language {language_id} was not found or has no code."
            )
        return language.languageCode

    @staticmethod
    def _match_sku(serial, supplier_skus):
        serial = str(serial or "")
        for supplier_sku in supplier_skus:
            if str(supplier_sku.get("sku_attr") or "") == serial:
                return supplier_sku
            if str(supplier_sku.get("sku_id") or "") == serial:
                return supplier_sku
        return None

    @staticmethod
    def _float_or_none(value):
        try:
            return float(value)
        except (TypeError, ValueError):
            return None

    @staticmethod
    def _int_or_none(value):
        try:
            return int(value)
        except (TypeError, ValueError):
            return None

    @staticmethod
    def _bool(value):
        if isinstance(value, bool):
            return value
        return str(value).strip().lower() in ("1", "true", "yes")


def get_supplier(seller_id):
    seller = Seller.objects.filter(id=seller_id).only("id", "keyName").first()
    if seller is None:
        raise SupplierDataError(f"Seller {seller_id} was not found.")
    if seller.keyName == "ali_express":
        return AliExpressSupplier()
    raise SupplierDataError(f"Seller {seller.id} is not supported.")


def validate_supplier_data(data):
    if not isinstance(data, dict):
        raise SupplierDataError("Supplier returned invalid SKU data.")
    stock = data.get("stock")
    if isinstance(stock, bool) or not isinstance(stock, int) or stock < 0:
        raise SupplierDataError("Supplier returned invalid stock data.")
    if not isinstance(data.get("prices", []), list):
        raise SupplierDataError("Supplier returned invalid price data.")
    if not isinstance(data.get("shippingOptions", []), list):
        raise SupplierDataError("Supplier returned invalid shipping data.")
    return data


def is_temporary_supplier_error(error):
    return isinstance(
        error,
        (AliExpressUpstreamError, ConnectionError, RequestException, TimeoutError),
    )
