# -*- coding: utf-8 -*-
import logging

from django.db import models
from django.db.models import functions as db_functions

from oscar.apps.partner.strategy import StockRequired as BaseStockRequired
from oscar.apps.partner.strategy import UseFirstStockRecord
from oscar.core.loading import get_class, get_classes

from ocyan.plugin.oscar.fender import InvalidTaxRateException

UnavailablePrice = get_class("partner.prices", "Unavailable")

ExcludingTaxPriceSelector, IncludingTaxPriceSelector = get_classes(
    "partner.taxbase", ["ExcludingTaxPriceSelector", "IncludingTaxPriceSelector"]
)

Unavailable = get_class("partner.availability", "Unavailable")
Available = get_class("partner.availability", "Available")
StockRequiredAvailability = get_class("partner.availability", "StockRequired")

logger = logging.getLogger(__name__)


class ProductSpecifiedTaxInclusive(object):
    """
    Pricing policiy where the price is declared on the stockrecord including
    tax and the tax rate is declared on the product.
    """

    def pricing_policy(self, product, stockrecord):
        if not stockrecord or stockrecord.price_excl_tax is None:
            return UnavailablePrice()

        try:
            return IncludingTaxPriceSelector(
                self.user,  # pylint: disable=no-member
                self.request,  # pylint: disable=no-member
                currency=stockrecord.price_currency,
                incl_tax=stockrecord.price_incl_tax,
                vat_rate=product.vat_rate,
            )
        except InvalidTaxRateException:
            logger.error("Tax missing for %s", product)
            return UnavailablePrice()

    def parent_pricing_policy(self, product, children_stock):
        stockrecords = [x[1] for x in children_stock if x[1] is not None]
        if not stockrecords:
            return UnavailablePrice()

        try:
            # We take price from first record
            stockrecord = stockrecords[0]

            return IncludingTaxPriceSelector(
                self.user,  # pylint: disable=no-member
                self.request,  # pylint: disable=no-member
                currency=stockrecord.price_currency,
                incl_tax=stockrecord.price_incl_tax,
                vat_rate=product.vat_rate,
            )
        except InvalidTaxRateException:
            logger.error("Tax missing for %s", product)
            return UnavailablePrice()


class ProductSpecifiedTaxExclusive(object):
    """
    Pricing policiy where the price is declared on the stockrecord excluding
    tax and the tax rate is declared on the product.
    """

    def pricing_policy(self, product, stockrecord):
        if not stockrecord or stockrecord.price_excl_tax is None:
            return UnavailablePrice()

        try:
            return ExcludingTaxPriceSelector(
                self.user,  # pylint: disable=no-member
                self.request,  # pylint: disable=no-member
                currency=stockrecord.price_currency,
                excl_tax=stockrecord.price_excl_tax,
                vat_rate=product.vat_rate,
            )
        except InvalidTaxRateException:
            logger.error("Tax missing for %s", product)
            return UnavailablePrice()

    def parent_pricing_policy(self, product, children_stock):
        stockrecords = [x[1] for x in children_stock if x[1] is not None]
        if not stockrecords:
            return UnavailablePrice()

        try:
            # We take price from first record
            stockrecord = stockrecords[0]

            return ExcludingTaxPriceSelector(
                self.user,  # pylint: disable=no-member
                self.request,  # pylint: disable=no-member
                currency=stockrecord.price_currency,
                excl_tax=stockrecord.price_excl_tax,
                vat_rate=product.vat_rate,
            )
        except InvalidTaxRateException:
            logger.error("Tax missing for %s", product)
            return UnavailablePrice()


class UseCheapestChildProductStockRecord(UseFirstStockRecord):
    def get_product_stockrecords_queryset(self, product):
        return product.stockrecords.filter(
            price_currency__isnull=False, price_excl_tax__isnull=False
        )

    def get_product_children_queryset(self, product):
        return product.variants_ordered_by_price.filter(
            is_public=True,
            stockrecords__price_currency__isnull=False,
            stockrecords__price_excl_tax__isnull=False,
        )

    def select_stockrecord(self, product):
        pc = product.get_product_class()
        if pc.track_stock:
            return (
                self.get_product_stockrecords_queryset(product)
                .annotate(
                    db_effective_stock=db_functions.Coalesce(
                        "num_in_stock", models.Value(0)
                    )
                    - db_functions.Coalesce("num_allocated", models.Value(0))
                )
                .annotate(
                    has_stock=models.Case(
                        models.When(db_effective_stock__gt=0, then=models.Value(True)),
                        default=models.Value(False),
                        output_field=models.BooleanField(),
                    )
                )
                .order_by("-has_stock", "price_excl_tax", "-db_effective_stock")
            ).first()
        else:
            return self.get_product_stockrecords_queryset(product).first()

    def select_children_stockrecords(self, product):
        """
        Select child with best availability
        """
        pc = product.get_product_class()
        valid_children = self.get_product_children_queryset(product)

        if pc.track_stock:
            first_available_child = (
                valid_children.annotate(
                    db_effective_stock=db_functions.Coalesce(
                        "stockrecords__num_in_stock", models.Value(0)
                    )
                    - db_functions.Coalesce(
                        "stockrecords__num_allocated", models.Value(0)
                    )
                ).filter(db_effective_stock__gt=0)
            ).first()
        else:
            first_available_child = valid_children.first()

        if first_available_child is not None:
            return [
                (first_available_child, self.select_stockrecord(first_available_child))
            ]

        return []


class StockRequired(BaseStockRequired):
    """
    Availability policy mixin for use with the ``Structured`` base strategy.
    This mixin ensures that a product can only be bought if it has stock
    available (if stock is being tracked).
    """

    def availability_policy(self, product, stockrecord):
        if not stockrecord:
            return Unavailable()
        if not product.get_product_class().track_stock:
            return Available()
        else:
            return StockRequiredAvailability(product, stockrecord)
