import logging
from collections import defaultdict
from contextlib import suppress
from typing import Collection, Any

from django.core.cache import cache
from django.db.models.functions import Length
from rest_framework.fields import JSONField, SerializerMethodField
from rest_framework.serializers import ModelSerializer
from rest_framework_recursive.fields import RecursiveField

from core.models import (
    Attribute,
    AttributeGroup,
    AttributeValue,
    Brand,
    Category,
    Feedback,
    Order,
    OrderProduct,
    Product,
    ProductImage,
    ProductTag,
    PromoCode,
    Promotion,
    Stock,
    Vendor,
    Wishlist,
)
from core.serializers.simple import CategorySimpleSerializer, ProductSimpleSerializer
from core.serializers.utility import AddressSerializer

logger = logging.getLogger("django")


class AttributeGroupDetailSerializer(ModelSerializer):
    children = RecursiveField(many=True)

    class Meta:
        model = AttributeGroup
        fields = [
            "uuid",
            "name",
            "children",
            "created",
            "modified",
        ]


class CategoryDetailSerializer(ModelSerializer):
    children = SerializerMethodField()
    image = SerializerMethodField()
    filterable_attributes = SerializerMethodField()

    class Meta:
        model = Category
        fields = [
            "uuid",
            "name",
            "description",
            "image",
            "markup_percent",
            "filterable_attributes",
            "children",
            "slug",
            "created",
            "modified",
        ]

    def get_image(self, obj: Category) -> str | None:
        with suppress(ValueError):
            return obj.image.url
        return None

    def get_filterable_attributes(self, obj: Category) -> list[dict]:
        cache_key = f"{obj.uuid}_filterable_results"
        filterable_results = cache.get(cache_key)
        if filterable_results is not None:
            return filterable_results

        attrs_qs = obj.attributes.filter(is_active=True, is_filterable=True)
        attributes = list(attrs_qs)

        attr_ids = [a.id for a in attributes]
        raw_vals = (
            AttributeValue.objects.annotate(value_length=Length("value"))
            .filter(
                attribute_id__in=attr_ids,
                product__category=obj,
                value_length__lte=30,
            )
            .values_list("attribute_id", "value")
            .distinct()
        )

        grouped = defaultdict(list)
        for attr_id, val in raw_vals:
            grouped[attr_id].append(val)

        filterable_results = []
        for attr in attributes:
            vals = grouped.get(attr.id, [])  # type: ignore
            slice_vals = vals[:128] if len(vals) > 128 else vals
            filterable_results.append(
                {
                    "attribute_name": attr.name,
                    "possible_values": slice_vals,
                    "value_type": attr.value_type,
                }
            )

        cache.set(cache_key, filterable_results, 3600)

        return filterable_results

    def get_children(self, obj) -> Collection[Any]:
        request = self.context.get("request")
        if request is not None and request.user.has_perm("view_category"):
            children = obj.children.all()
        else:
            children = obj.children.filter(is_active=True)

        return CategoryDetailSerializer(children, many=True, context=self.context).data if obj.children.exists() else []


class BrandDetailSerializer(ModelSerializer):
    categories = CategorySimpleSerializer(many=True)
    small_logo = SerializerMethodField()
    big_logo = SerializerMethodField()

    class Meta:
        model = Brand
        fields = [
            "uuid",
            "name",
            "categories",
            "created",
            "modified",
            "big_logo",
            "small_logo",
        ]

    def get_small_logo(self, obj: Brand) -> str | None:
        with suppress(ValueError):
            return obj.small_logo.url
        return None

    def get_big_logo(self, obj: Brand) -> str | None:
        with suppress(ValueError):
            return obj.big_logo.url
        return None


class BrandProductDetailSerializer(ModelSerializer):
    small_logo = SerializerMethodField()
    big_logo = SerializerMethodField()

    class Meta:
        model = Brand
        fields = [
            "uuid",
            "name",
            "created",
            "modified",
            "big_logo",
            "small_logo",
        ]

    def get_small_logo(self, obj: Brand) -> str | None:
        with suppress(ValueError):
            return obj.small_logo.url
        return None

    def get_big_logo(self, obj: Brand) -> str | None:
        with suppress(ValueError):
            return obj.big_logo.url
        return None


class ProductTagDetailSerializer(ModelSerializer):
    class Meta:
        model = ProductTag
        fields = [
            "uuid",
            "tag_name",
            "name",
            "created",
            "modified",
        ]


class ProductImageDetailSerializer(ModelSerializer):
    image = SerializerMethodField()

    class Meta:
        model = ProductImage
        fields = [
            "uuid",
            "alt",
            "image",
            "priority",
            "created",
            "modified",
        ]

    def get_image(self, obj: ProductImage) -> str:
        return obj.image.url or ""


class AttributeDetailSerializer(ModelSerializer):
    categories = CategoryDetailSerializer(many=True)
    group = AttributeGroupDetailSerializer()

    class Meta:
        model = Attribute
        fields = [
            "uuid",
            "name",
            "value_type",
            "categories",
            "group",
            "created",
            "modified",
        ]


class AttributeInnerSerializer(ModelSerializer):
    group = AttributeGroupDetailSerializer()

    class Meta:
        model = Attribute
        fields = [
            "uuid",
            "name",
            "value_type",
            "group",
            "created",
            "modified",
        ]


class AttributeValueDetailSerializer(ModelSerializer):
    attribute = AttributeInnerSerializer()

    class Meta:
        model = AttributeValue
        fields = [
            "uuid",
            "attribute",
            "value",
            "created",
            "modified",
        ]


class VendorDetailSerializer(ModelSerializer):
    class Meta:
        model = Vendor
        fields = [
            "uuid",
            "name",
            "authentication",
            "markup_percent",
            "created",
            "modified",
        ]


class StockDetailSerializer(ModelSerializer):
    vendor = VendorDetailSerializer()

    class Meta:
        model = Stock
        fields = [
            "uuid",
            "vendor",
            "price",
            "purchase_price",
            "quantity",
            "sku",
            "digital_asset",
            "created",
            "modified",
        ]


class PromoCodeDetailSerializer(ModelSerializer):
    class Meta:
        model = PromoCode
        fields = [
            "uuid",
            "code",
            "discount_amount",
            "discount_percent",
            "start_time",
            "end_time",
            "used_on",
            "created",
            "modified",
        ]


class ProductDetailSerializer(ModelSerializer):
    brand = BrandProductDetailSerializer()
    category = CategorySimpleSerializer()
    tags = ProductTagDetailSerializer(
        many=True,
    )
    images = ProductImageDetailSerializer(
        many=True,
    )
    attributes = AttributeValueDetailSerializer(
        many=True,
    )

    rating = SerializerMethodField()
    price = SerializerMethodField()
    quantity = SerializerMethodField()
    feedbacks_count = SerializerMethodField()

    class Meta:
        model = Product
        fields = [
            "uuid",
            "sku",
            "name",
            "description",
            "partnumber",
            "is_digital",
            "brand",
            "category",
            "feedbacks_count",
            "quantity",
            "tags",
            "slug",
            "images",
            "attributes",
            "rating",
            "price",
            "created",
            "modified",
        ]

    def get_rating(self, obj: Product) -> float:
        return obj.rating

    def get_price(self, obj: Product) -> float:
        return obj.price

    def get_feedbacks_count(self, obj: Product) -> int:
        return obj.feedbacks_count

    def get_quantity(self, obj: Product) -> int:
        return obj.quantity


class PromotionDetailSerializer(ModelSerializer):
    products = ProductDetailSerializer(
        many=True,
    )

    class Meta:
        model = Promotion
        fields = [
            "uuid",
            "name",
            "discount_percent",
            "description",
            "products",
            "created",
            "modified",
        ]


class WishlistDetailSerializer(ModelSerializer):
    products = ProductSimpleSerializer(
        many=True,
    )

    class Meta:
        model = Wishlist
        fields = [
            "uuid",
            "products",
            "created",
            "modified",
        ]


class OrderProductDetailSerializer(ModelSerializer):
    product = ProductDetailSerializer()
    billing_address = AddressSerializer(read_only=True, required=False)
    shipping_address = AddressSerializer(read_only=True, required=False)
    attributes = JSONField(required=False)

    class Meta:
        model = OrderProduct
        fields = [
            "uuid",
            "product",
            "quantity",
            "buy_price",
            "comments",
            "notifications",
            "billing_address",
            "shipping_address",
            "attributes",
            "status",
            "created",
            "modified",
        ]


class FeedbackDetailSerializer(ModelSerializer):
    order_product = OrderProductDetailSerializer()

    class Meta:
        model = Feedback
        fields = [
            "uuid",
            "rating",
            "comment",
            "order_product",
            "created",
            "modified",
        ]


class OrderDetailSerializer(ModelSerializer):
    promo_code = PromoCodeDetailSerializer()
    order_products = OrderProductDetailSerializer(
        many=True,
    )
    total_price = SerializerMethodField(read_only=True)

    class Meta:
        model = Order
        fields = [
            "uuid",
            "status",
            "total_price",
            "promo_code",
            "billing_address",
            "shipping_address",
            "buy_time",
            "order_products",
            "human_readable_id",
            "created",
            "modified",
        ]

    def get_total_price(self, obj: Order) -> float:
        return obj.total_price
