from django.db import transaction
from django.utils import timezone

from basic.models import Language
from shop.models import (
    Collection,
    Currency,
    Product,
    ProductCollection,
    ProductLanguage,
    ProductOption,
    ProductOptionLanguage,
    ProductOptionValue,
    ProductOptionValueLanguage,
    SellerShippingCost,
    ShippingCost,
    SKU,
    SKUImage,
    SKUOptionValue,
    SKUPrice,
)


PRODUCT_RESPONSE_KEYS = (
    "aliexpress_ds_product_get_response",
    "aliexpress_ds_product_wholesale_get_response",
)


@transaction.atomic
def saveProduct(productData, currencyId, languageId, collectionId):
    """Save an AliExpress product response in the shop models."""
    result = _getProductResult(productData)
    baseInfo = result.get("ae_item_base_info_dto") or {}
    skuDataList = _getItems(
        result.get("ae_item_sku_info_dtos"),
        "ae_item_sku_info_d_t_o",
    )

    if not skuDataList:
        raise ValueError("productData does not contain SKU data")

    currency = Currency.objects.get(id=currencyId)
    language = Language.objects.get(id=languageId)
    collection = Collection.objects.get(id=collectionId)
    now = timezone.now()

    productId = (
        (result.get("product_id_converter_result") or {}).get("main_product_id")
        or baseInfo.get("product_id")
    )
    if productId is None:
        raise ValueError("productData does not contain a product id")

    productKey = f"ali_express_{productId}"
    product = Product.objects.select_for_update().filter(keyName=productKey).first()
    if product is None:
        product = Product.objects.create(
            keyName=productKey,
            isDigital=False,
            isFree=False,
            createdDate=now,
            updatedDate=now,
        )
    else:
        product.isDigital = False
        product.isFree = False
        product.updatedDate = now
        product.save(update_fields=("isDigital", "isFree", "updatedDate"))

    productLanguage = ProductLanguage.objects.filter(
        productId=product,
        languageId=language,
    ).first()
    if productLanguage is None:
        ProductLanguage.objects.create(
            productId=product,
            languageId=language,
            name=baseInfo.get("subject"),
            createdDate=now,
            updatedDate=now,
        )
    else:
        productLanguage.name = baseInfo.get("subject")
        productLanguage.updatedDate = now
        productLanguage.save(update_fields=("name", "updatedDate"))

    if not ProductCollection.objects.filter(
        productId=product,
        collectionId=collection,
    ).exists():
        ProductCollection.objects.create(
            productId=product,
            collectionId=collection,
            createdDate=now,
            updatedDate=now,
        )

    optionMap = {}
    optionValueMap = {}
    productImages = _getProductImages(result)
    freightBySku = _getFreightBySku(productData)
    sellerShippingCost = None

    for skuData in skuDataList:
        serial = skuData.get("sku_attr")
        serial = str(serial) if serial is not None else ""
        sku = SKU.objects.filter(productId=product, serial=serial).first()
        if sku is None:
            sku = SKU.objects.create(
                productId=product,
                serial=serial,
                stock=_getStock(skuData),
                createdDate=now,
                updatedDate=now,
            )
        else:
            sku.stock = _getStock(skuData)
            sku.updatedDate = now
            sku.save(update_fields=("stock", "updatedDate"))

        priceValues = {
            "salePrice": _toFloat(
                skuData.get("offer_sale_price")
                or skuData.get("offer_bulk_sale_price")
                or skuData.get("sku_price")
            ),
            "originalPrice": _toFloat(skuData.get("sku_price")),
        }
        skuPrice = SKUPrice.objects.filter(
            skuId=sku,
            currencyId=currency,
        ).first()
        if skuPrice is None:
            skuPrice = SKUPrice.objects.create(
                skuId=sku,
                currencyId=currency,
                **priceValues,
                createdDate=now,
                updatedDate=now,
            )
        else:
            skuPrice.salePrice = priceValues["salePrice"]
            skuPrice.originalPrice = priceValues["originalPrice"]
            skuPrice.updatedDate = now
            skuPrice.save(
                update_fields=("salePrice", "originalPrice", "updatedDate")
            )

        SKUOptionValue.objects.filter(skuId=sku).delete()
        SKUImage.objects.filter(skuId=sku).delete()

        skuImages = []
        skuProperties = _getItems(
            skuData.get("ae_sku_property_dtos"),
            "ae_sku_property_d_t_o",
        )

        for skuProperty in skuProperties:
            option = _getOrCreateOption(
                product,
                language,
                skuProperty,
                optionMap,
                now,
            )
            optionValue = _getOrCreateOptionValue(
                option,
                language,
                skuProperty,
                optionValueMap,
                now,
            )
            SKUOptionValue.objects.create(
                skuId=sku,
                productOptionValueId=optionValue,
                createdDate=now,
                updatedDate=now,
            )

            imageUrl = skuProperty.get("sku_image")
            if imageUrl and imageUrl != "0":
                skuImages.append(imageUrl)

        if not skuImages:
            skuImages = productImages[:1]

        for imageUrl in dict.fromkeys(skuImages):
            SKUImage.objects.create(
                skuId=sku,
                imageUrl=imageUrl,
                createdDate=now,
                updatedDate=now,
            )

        deliveryOption = _getDeliveryOption(
            freightBySku.get(str(skuData.get("sku_id")))
        )
        if deliveryOption:
            if sellerShippingCost is None:
                sellerShippingCost = _getSellerShippingCost(now)

            shippingCost = ShippingCost.objects.filter(
                skuPriceId=skuPrice,
                sellerShippingCostId=sellerShippingCost,
            ).first()
            shippingValues = {
                "isFreeShipping": _toBool(deliveryOption.get("free_shipping")),
                "minDeliveryDays": _toIntOrNone(
                    deliveryOption.get("min_delivery_days")
                ),
                "maxDeliveryDays": _toIntOrNone(
                    deliveryOption.get("max_delivery_days")
                ),
                "cost": _toFloat(deliveryOption.get("shipping_fee_cent")) or 0.0,
            }
            if shippingCost is None:
                ShippingCost.objects.create(
                    skuPriceId=skuPrice,
                    sellerShippingCostId=sellerShippingCost,
                    **shippingValues,
                    createdDate=now,
                    updatedDate=now,
                )
            else:
                for field, value in shippingValues.items():
                    setattr(shippingCost, field, value)
                shippingCost.updatedDate = now
                shippingCost.save(
                    update_fields=(*shippingValues.keys(), "updatedDate")
                )

    return product


def _getProductResult(productData):
    if not isinstance(productData, dict):
        raise ValueError("productData must be a dictionary")

    productJson = productData.get("productJson", productData)
    if not isinstance(productJson, dict):
        raise ValueError("productData.productJson must be a dictionary")

    for responseKey in PRODUCT_RESPONSE_KEYS:
        if responseKey in productJson:
            return (productJson.get(responseKey) or {}).get("result") or {}

    return productJson.get("result", productJson)


def _getItems(container, itemKey):
    if isinstance(container, dict) and itemKey in container:
        container = container[itemKey]

    if isinstance(container, list):
        return container
    if isinstance(container, dict) and container:
        return [container]
    return []


def _getOrCreateOption(product, language, skuProperty, optionMap, now):
    optionId = skuProperty.get("sku_property_id")
    optionName = str(skuProperty.get("sku_property_name") or "option")
    optionKey = f"option_{optionId}" if optionId is not None else optionName

    if optionKey not in optionMap:
        option = ProductOption.objects.filter(
            productId=product,
            keyName=optionKey,
        ).first()
        if option is None:
            option = ProductOption.objects.create(
                productId=product,
                keyName=optionKey,
                createdDate=now,
                updatedDate=now,
            )
        else:
            option.updatedDate = now
            option.save(update_fields=("updatedDate",))

        optionLanguage = ProductOptionLanguage.objects.filter(
            productOptionId=option,
            languageId=language,
        ).first()
        if optionLanguage is None:
            ProductOptionLanguage.objects.create(
                productOptionId=option,
                languageId=language,
                name=optionName,
                createdDate=now,
                updatedDate=now,
            )
        else:
            optionLanguage.name = optionName
            optionLanguage.updatedDate = now
            optionLanguage.save(update_fields=("name", "updatedDate"))
        optionMap[optionKey] = option

    return optionMap[optionKey]


def _getOrCreateOptionValue(
    option,
    language,
    skuProperty,
    optionValueMap,
    now,
):
    valueId = skuProperty.get("property_value_id")
    value = str(skuProperty.get("sku_property_value") or "value")
    valueName = str(skuProperty.get("property_value_definition_name") or value)
    valueKey = f"value_{valueId}" if valueId is not None else value
    mapKey = (option.id, valueKey)

    if mapKey not in optionValueMap:
        optionValue = ProductOptionValue.objects.filter(
            productOptionId=option,
            keyName=valueKey,
        ).first()
        if optionValue is None:
            optionValue = ProductOptionValue.objects.create(
                productOptionId=option,
                keyName=valueKey,
                value=value,
                createdDate=now,
                updatedDate=now,
            )
        else:
            optionValue.value = value
            optionValue.updatedDate = now
            optionValue.save(update_fields=("value", "updatedDate"))

        optionValueLanguage = ProductOptionValueLanguage.objects.filter(
            productOptionValueId=optionValue,
            languageId=language,
        ).first()
        if optionValueLanguage is None:
            ProductOptionValueLanguage.objects.create(
                productOptionValueId=optionValue,
                languageId=language,
                name=valueName,
                createdDate=now,
                updatedDate=now,
            )
        else:
            optionValueLanguage.name = valueName
            optionValueLanguage.updatedDate = now
            optionValueLanguage.save(update_fields=("name", "updatedDate"))
        optionValueMap[mapKey] = optionValue

    return optionValueMap[mapKey]


def _getProductImages(result):
    imageUrls = (result.get("ae_multimedia_info_dto") or {}).get("image_urls") or ""
    if isinstance(imageUrls, list):
        return [str(imageUrl).strip() for imageUrl in imageUrls if imageUrl]
    return [imageUrl.strip() for imageUrl in str(imageUrls).split(";") if imageUrl]


def _getFreightBySku(productData):
    if not isinstance(productData, dict):
        return {}

    return {
        str(item.get("skuId")): item.get("freightJson")
        for item in productData.get("freightResults", [])
        if isinstance(item, dict) and item.get("skuId") is not None
    }


def _getDeliveryOption(freightJson):
    if not isinstance(freightJson, dict):
        return None

    response = freightJson.get("aliexpress_ds_freight_query_response", freightJson)
    result = response.get("result", response) if isinstance(response, dict) else {}
    options = _getItems(
        (result.get("delivery_options") or {}) if isinstance(result, dict) else {},
        "delivery_option_d_t_o",
    )
    return options[0] if options else None


def _getSellerShippingCost(now):
    sellerShippingCost = SellerShippingCost.objects.filter(
        keyName="ali_express"
    ).first()
    if sellerShippingCost:
        return sellerShippingCost

    return SellerShippingCost.objects.create(
        keyName="ali_express",
        createdDate=now,
        updatedDate=now,
    )


def _getStock(skuData):
    if skuData.get("sku_stock") is False:
        return 0

    stock = skuData.get("sku_available_stock")
    if stock is None:
        stock = skuData.get("ipm_sku_stock")
    return _toInt(stock)


def _toFloat(value):
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def _toInt(value):
    try:
        return int(value)
    except (TypeError, ValueError):
        return 0


def _toIntOrNone(value):
    if value is None or value == "":
        return None
    return _toInt(value)


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