import uuid

from django.apps import apps
from django.conf import settings
from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from vendor.config import DEFAULT_CURRENCY, VENDOR_PRODUCT_MODEL
from vendor.fields import AutoSlugField

from .modelmanagers import SoftDeleteManager
from .utils import is_currency_available
from .validator import validate_msrp


def get_product_model():
    return apps.get_model(VENDOR_PRODUCT_MODEL)


##################
# DEFAULTS
##################
# TODO: Nice to have class MSRP(NestedModels)
def product_meta_default():
    return {"msrp": {"default": DEFAULT_CURRENCY, DEFAULT_CURRENCY: 0.00}}


def product_description_default():
    return {"description": "", "call_out": " "}


def create_sku_if_empty(instance):
    return f"{instance.site.pk}-{instance.name}"


##################
# BASE MODELS
##################


class CreateUpdateModelBase(models.Model):
    """
    This is a shared models base that provides created & updated timestamp fields
    """

    created = models.DateTimeField("date created", auto_now_add=True)
    updated = models.DateTimeField("last updated", auto_now=True)

    class Meta:
        abstract = True


class SoftDeleteModelBase(models.Model):
    """
    Adds "Soft Delete" functionality to models
    """

    deleted = models.BooleanField(_("Deleted"), default=False)

    # Managers
    objects = models.Manager()  # Standard Model manager
    not_deleted = SoftDeleteManager()  # Records filtered with deleted=False

    class Meta:
        abstract = True

    def delete(self, using=None, keep_parents=False):
        self.deleted = True
        return self.save()


class ProductModelBase(CreateUpdateModelBase):
    """
    This is the base class that all Products should inherit from.
    """

    sku = models.CharField(
        _("SKU"),
        max_length=40,
        unique=True,
        blank=True,
        null=True,
        help_text=_("User Defineable SKU field"),
    )  # Needs to be autogenerated by default, and unique from the PK
    uuid = models.UUIDField(
        default=uuid.uuid4, editable=False, unique=True
    )  # Used to track the product
    name = models.CharField(_("Name"), max_length=80, blank=False)
    site = models.ForeignKey(
        Site,
        verbose_name=_("Site"),
        on_delete=models.CASCADE,
        default=settings.SITE_ID,
        related_name="products",
    )  # For multi-site support
    slug = AutoSlugField(
        populate_from="name", unique_with="site__id"
    )  # Gets set in the save
    available = models.BooleanField(
        _("Available"), default=False, help_text=_("Is this currently available?")
    )  # This can be forced to be unavailable if there is no prices attached.
    description = models.JSONField(
        _("Description"),
        default=product_description_default,
        blank=True,
        null=True,
        help_text=_("Eg: {'call out': 'The ultimate product'}"),
    )
    meta = models.JSONField(
        _("Meta"),
        validators=[validate_msrp],
        default=product_meta_default,
        blank=True,
        null=True,
        help_text=_(
            "Eg: { 'msrp':{'usd':10.99} }\n(iso4217 Country Code):(MSRP Price)"
        ),
    )
    classification = models.ManyToManyField(
        "vendor.TaxClassifier", blank=True
    )  # What taxes can apply to this item

    # NOTE: These are defined here since this is a base class and we want to be able to query
    #       these relationships consistently across products.  If they were defined on the
    #       child classes, querying would be inconsistent.
    offers = models.ManyToManyField("vendor.Offer", blank=True, related_name="products")
    receipts = models.ManyToManyField(
        "vendor.Receipt", blank=True, related_name="products"
    )

    objects = models.Manager()
    on_site = CurrentSiteManager()

    class Meta:
        abstract = True

    def __str__(self):
        return self.name

    def get_msrp(self, currency):
        if currency in self.meta["msrp"]:
            return self.meta["msrp"][currency]
        else:
            return self.meta["msrp"][self.meta["msrp"]["default"]]

    def add_to_cart_url(self):
        """
        Link to add the item to the user's cart.
        """

    # TODO: ADD trigger when object becomes unavailable to disable offer if it exisits.

    def get_best_currency(self, currency=DEFAULT_CURRENCY):
        """
        If no currency is provided as an argument it will default to the products's msrp default currency.
        If currency is provided but is not available in the product it will default to the products's msrp default currency.  # noqa: E501
        """
        if is_currency_available(self.meta["msrp"].keys(), currency=currency):
            return currency
        else:
            return self.meta["msrp"]["default"]

    def active_profile_receipts(self):
        """
        Gets currently active reciepts by checking if the customer owns the product
        """
        now = timezone.now()
        return self.receipts.select_related("profile").filter(
            models.Q(deleted=False),
            models.Q(start_date__lte=now) | models.Q(start_date=None),
            models.Q(end_date__gte=now) | models.Q(end_date=None),
        )

    def owners(self):
        """
        Gets a set list of profiles that own the product
        """
        active_receipts = self.active_profile_receipts()
        return set([receipt.profile for receipt in active_receipts])

    def inactive_profile_receipts(self):
        """
        Gets a list of reciepts for customers that no longer own the product.
        """
        now = timezone.now()
        return (
            self.receipts.select_related("profile")
            .filter(deleted=False)
            .exclude(
                models.Q(start_date__lte=now) | models.Q(start_date=None),
                models.Q(end_date__gte=now) | models.Q(end_date=None),
            )
        )

    def expired_owners(self):
        """
        Gets a set list of profiles that no longer own the product
        """
        owners = self.owners()
        inactive_receipts = self.inactive_profile_receipts()
        return set(
            [
                receipt.profile
                for receipt in inactive_receipts
                if receipt.profile not in owners
            ]
        )

    def get_current_offer(self):
        """Returns the current offer for the product, if an active one exists. # noqa: E501"""
        now = timezone.now()
        return (
            self.offers.filter(
                available=True,
                start_date__lte=now,
            )
            .filter(Q(end_date__isnull=True) | Q(end_date__gte=now))
            .first()
        )

    def get_current_price(self, currency=DEFAULT_CURRENCY):
        """Returns the current price for the product, if an active one exists. # noqa: E501"""
        offer = self.get_current_offer()
        return offer.current_price(currency) if offer else None

    def save(self, *args, **kwargs):
        if not self.sku:
            self.sku = create_sku_if_empty(self)
        super().save(*args, **kwargs)
