import logging
from contextlib import suppress
from datetime import timedelta
from typing import Any

from django.db import IntegrityError
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.crypto import get_random_string
from django.utils.http import urlsafe_base64_decode
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from sentry_sdk import capture_exception

from core.crm import any_crm_integrations
from core.crm.exceptions import CRMException
from core.models import Category, DigitalAssetDownload, Order, Product, PromoCode, Wishlist
from core.utils import (
    generate_human_readable_id,
    resolve_translations_for_elasticsearch,
)
from core.utils.emailing import send_order_created_email, send_order_finished_email, send_promocode_created_email
from evibes.utils.misc import create_object
from vibes_auth.models import User

logger = logging.getLogger("django")


# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_order_on_user_creation_signal(instance: User, created: bool, **kwargs: dict[Any, Any]) -> None:
    if created:
        try:
            Order.objects.create(user=instance, status="PENDING")
        except IntegrityError:
            human_readable_id = generate_human_readable_id()
            while True:
                if Order.objects.filter(human_readable_id=human_readable_id).exists():
                    human_readable_id = generate_human_readable_id()
                    continue
                Order.objects.create(user=instance, status="PENDING", human_readable_id=human_readable_id)
                break


# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_wishlist_on_user_creation_signal(instance: User, created: bool, **kwargs: dict[Any, Any]) -> None:
    if created:
        Wishlist.objects.create(user=instance)


# noinspection PyUnusedLocal
@receiver(post_save, sender=User)
def create_promocode_on_user_referring(instance: User, created: bool, **kwargs: dict[Any, Any]) -> None:
    try:
        if type(instance.attributes) is not dict:
            instance.attributes = {}
            instance.save()

        if created and instance.attributes.get("referrer", ""):
            referrer_uuid = urlsafe_base64_decode(instance.attributes.get("referrer", "")).decode()
            referrer = User.objects.get(uuid=referrer_uuid)
            code = f"WELCOME-{get_random_string(6)}"
            PromoCode.objects.create(
                user=referrer,
                code=code if len(code) <= 20 else code[:20],
                discount_percent=10,
                start_time=now(),
                end_time=now() + timedelta(days=30),
            )
    except Exception as e:
        capture_exception(e)
        logger.error(_(f"error during promocode creation: {e!s}"))


# noinspection PyUnusedLocal
@receiver(post_save, sender=Order)
def process_order_changes(instance: Order, created: bool, **kwargs: dict[Any, Any]):
    if type(instance.attributes) is not dict:
        instance.attributes = {}

    if any_crm_integrations() and instance.status != "PENDING":
        with suppress(CRMException):
            instance.trigger_crm()

    if not created:
        if instance.status != "PENDING" and instance.user:
            pending_orders = Order.objects.filter(user=instance.user, status="PENDING")

            if not pending_orders.exists():
                try:
                    Order.objects.create(user=instance.user, status="PENDING")
                except IntegrityError:
                    human_readable_id = generate_human_readable_id()
                    while True:
                        try:
                            if Order.objects.filter(human_readable_id=human_readable_id).exists():
                                human_readable_id = generate_human_readable_id()
                                continue
                            Order.objects.create(
                                user=instance,
                                status="PENDING",
                                human_readable_id=human_readable_id,
                            )
                            break
                        except IntegrityError:
                            continue

        if instance.status in ["CREATED", "PAYMENT"]:
            if not instance.is_whole_digital:
                send_order_created_email.delay(instance.uuid)  # type: ignore [attr-defined]

            for order_product in instance.order_products.filter(status="DELIVERING", product__is_digital=True):
                if not order_product.product:
                    continue

                stocks_qs = order_product.product.stocks.filter(digital_asset__isnull=False).exclude(digital_asset="")

                stock = stocks_qs.first()

                has_file = False
                if stock:
                    f = stock.digital_asset
                    has_file = bool(getattr(f, "name", "")) and f.storage.exists(f.name)

                if has_file:
                    order_product.status = "FINISHED"
                    if not order_product.download:
                        DigitalAssetDownload.objects.create(order_product=order_product)
                        order_product.order.user.payments_balance.amount -= order_product.buy_price  # type: ignore [union-attr, operator]
                        order_product.order.user.payments_balance.save()  # type: ignore [union-attr]
                        order_product.save()
                        continue

                order_product.save()

                try:
                    vendor_name = (
                        order_product.product.stocks.filter(price=order_product.buy_price).first().vendor.name.lower()  # type: ignore [union-attr, attr-defined, misc]
                    )

                    vendor = create_object(f"core.vendors.{vendor_name}", f"{vendor_name.title()}Vendor")

                    vendor.buy_order_product(order_product)  # type: ignore [attr-defined]

                except Exception as e:
                    order_product.add_error(f"Failed to buy {order_product.uuid}. Reason: {e}...")

            else:
                instance.finalize()

            if instance.order_products.filter(status="FAILED").count() == instance.order_products.count():
                instance.status = "FAILED"
                instance.save()

        if instance.status == "FINISHED" and not instance.attributes.get("system_email_sent", False):
            instance.attributes["system_email_sent"] = True
            instance.save()
            send_order_finished_email.delay(instance.uuid)  # type: ignore [attr-defined]


# noinspection PyUnusedLocal
@receiver(post_save, sender=Product)
def update_product_name_lang(instance: Product, created: bool, **kwargs: dict[Any, Any]) -> None:
    if created:
        pass
    resolve_translations_for_elasticsearch(instance, "name")
    resolve_translations_for_elasticsearch(instance, "description")


# noinspection PyUnusedLocal
@receiver(post_save, sender=Category)
def update_category_name_lang(instance: Category, created: bool, **kwargs: dict[Any, Any]) -> None:
    if created:
        pass
    resolve_translations_for_elasticsearch(instance, "name")
    resolve_translations_for_elasticsearch(instance, "description")


# noinspection PyUnusedLocal
@receiver(post_save, sender=PromoCode)
def send_promocode_creation_email(instance: PromoCode, created: bool, **kwargs: dict[Any, Any]) -> None:
    if created:
        send_promocode_created_email.delay(instance.uuid)  # type: ignore [attr-defined]
