# pylint: disable=W0223
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from rest_framework import exceptions, serializers

from oscar.core import prices
from oscar.core.loading import get_class, get_model
from oscar.templatetags.currency_filters import currency

from oscarapi.basket.operations import assign_basket_strategy
from oscarapi.serializers.checkout import (
    CheckoutSerializer as BaseCheckoutSerializer,
)
from oscarapi.serializers.checkout import (
    PriceSerializer as BasePriceSerializer,
)
from oscarapi.serializers.checkout import (
    ShippingAddressSerializer as BaseShippingAddressSerializer,
)
from oscarapi.serializers.checkout import (
    ShippingMethodSerializer as BaseShippingMethodSerializer,
)
from oscarapi.serializers.fields import TaxIncludedDecimalField

from ocyan.main.loading import get_ocyan_class

SurchargeApplicator = get_class("checkout.applicator", "SurchargeApplicator")
OrderTotalCalculator = get_class("checkout.calculators", "OrderTotalCalculator")
PhoneNumberFormContext = get_ocyan_class(
    "checkoutapi.phone_number", "PhoneNumberFormContext"
)

ShippingAddress = get_model("order", "ShippingAddress")
BillingAddress = get_model("order", "BillingAddress")


class PriceSerializer(BasePriceSerializer):
    excl_tax = serializers.DecimalField(decimal_places=4, max_digits=12, required=True)
    incl_tax = TaxIncludedDecimalField(
        excl_tax_field="excl_tax", decimal_places=4, max_digits=12, required=False
    )
    tax = TaxIncludedDecimalField(
        excl_tax_value="0.00", decimal_places=4, max_digits=12, required=False
    )
    formatted_excl_tax = serializers.SerializerMethodField()

    def get_formatted_excl_tax(self, obj):
        return currency(obj.excl_tax, obj.currency)


class PaymentMethodSerializer(serializers.Serializer):
    country = serializers.CharField(max_length=2, required=False, allow_blank=True)
    key = serializers.CharField(required=False, allow_blank=True)
    label = serializers.CharField(required=False, allow_blank=True)
    snippet = serializers.CharField(allow_blank=True, required=False)


class ShippingMethodSerializer(BaseShippingMethodSerializer):
    def calculate_price(self, obj):
        price = obj.calculate(self.context.get("basket"))
        return PriceSerializer(price).data


class ShippingAddressSerializer(BaseShippingAddressSerializer):
    line1 = serializers.CharField(max_length=255, required=False, allow_blank=True)
    phone_number = serializers.ModelField(
        model_field=PhoneNumberField(blank=True), validators=[]
    )

    def validate(self, attrs):
        data = super().validate(attrs)
        phonectx = PhoneNumberFormContext(data)
        data["phone_number"] = phonectx.clean_phone_number_field("phone_number")
        return data

    def add_error(self, field_name, error):
        raise serializers.ValidationError(error)


class ShippingCountrySerializer(ShippingAddressSerializer):
    # we only care about the country, the rest is nice, but optional
    house_number = serializers.CharField(
        required=False, allow_null=True, allow_blank=True, max_length=255
    )


class OrderTotalSerializer(serializers.Serializer):
    total = PriceSerializer(many=False)
    shipping_charge = PriceSerializer(many=False)
    total_incl_tax_excl_discounts = serializers.DecimalField(
        decimal_places=2, max_digits=12, source="basket.total_incl_tax_excl_discounts"
    )
    price_excl_tax_excl_discounts = serializers.DecimalField(
        decimal_places=2, max_digits=12, source="basket.total_excl_tax_excl_discounts"
    )
    snippet = serializers.CharField(allow_blank=True, required=False)


class CheckoutSerializer(BaseCheckoutSerializer):
    shipping_address = ShippingAddressSerializer(many=False, required=False)
    payment_method = serializers.CharField(required=False)
    shipping_charge = PriceSerializer(many=False, required=False)

    @property
    def request(self):
        return self.context["request"]

    def validate(self, attrs):
        request = self.request

        if request.user.is_anonymous:
            if not settings.OSCAR_ALLOW_ANON_CHECKOUT:
                message = _("Anonymous checkout forbidden")
                raise serializers.ValidationError(message)

            if not self.partial and not attrs.get("guest_email"):
                # Always require the guest email field if the user is anonymous
                message = _("Guest email is required for anonymous checkouts")
                raise serializers.ValidationError(message)
        else:
            if "guest_email" in attrs:
                # Don't store guest_email field if the user is authenticated
                del attrs["guest_email"]

        basket = attrs.get("basket")
        shipping_address = attrs.get("shipping_address")

        if (  # this code is only needed for vat plugin
            shipping_address is not None and "country" in shipping_address
        ):
            country = shipping_address["country"]
            request.META["shipping_country"] = country.pk

        basket = assign_basket_strategy(basket, request)
        if basket.num_items <= 0:
            message = _("Cannot checkout with empty basket")
            raise serializers.ValidationError(message)

        shipping_method = self._shipping_method(
            request, basket, attrs.get("shipping_method_code"), shipping_address
        )

        shipping_charge = shipping_method.calculate(basket)
        posted_shipping_charge = attrs.get("shipping_charge")

        if posted_shipping_charge is not None:
            posted_shipping_charge = prices.Price(**posted_shipping_charge)
            # test submitted data.
            if not posted_shipping_charge == shipping_charge:
                message = _(
                    "Shipping price incorrect %s != %s"
                    % (posted_shipping_charge, shipping_charge)
                )
                raise serializers.ValidationError(message)

        posted_total = attrs.get("total")
        surcharges = SurchargeApplicator(request, attrs).get_applicable_surcharges(
            basket, shipping_charge=shipping_charge
        )
        total = OrderTotalCalculator().calculate(basket, shipping_charge, surcharges)
        if posted_total is not None:
            if posted_total != total.incl_tax:
                message = _("Total incorrect %s != %s" % (posted_total, total.incl_tax))
                raise serializers.ValidationError(message)

        # update attrs with validated data.
        attrs["total"] = total
        attrs["shipping_method"] = shipping_method
        attrs["shipping_charge"] = shipping_charge
        attrs["basket"] = basket
        attrs["surcharges"] = surcharges
        return attrs

    def create(self, validated_data):
        try:
            basket = validated_data.get("basket")
            order_number = self.generate_order_number(basket)
            request = self.request

            if "shipping_address" in validated_data:
                shipping_address = ShippingAddress(**validated_data["shipping_address"])
            else:
                shipping_address = None

            if "billing_address" in validated_data:
                billing_address = BillingAddress(**validated_data["billing_address"])
            else:
                billing_address = None

            return self.place_order(
                order_number=order_number,
                user=request.user,
                basket=basket,
                shipping_address=shipping_address,
                shipping_method=validated_data.get("shipping_method"),
                shipping_charge=validated_data.get("shipping_charge"),
                surcharges=validated_data.get("surcharges"),
                billing_address=billing_address,
                order_total=validated_data.get("total"),
                guest_email=validated_data.get("guest_email") or "",
            )
        except ValueError as e:
            raise exceptions.NotAcceptable(str(e))
