import copy
import logging
import operator

from django import http
from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import transaction
from django.forms.models import model_to_dict
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.functional import cached_property
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import activate, gettext
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from rest_framework import response
from rest_framework.reverse import reverse
from rest_framework.settings import api_settings

from oscar.apps.checkout.exceptions import FailedPreCondition
from oscar.apps.checkout.views import ThankYouView as BaseThankYouView
from oscar.core.loading import feature_hidden, get_class, get_classes, get_model
from oscar.core.prices import Price

from oscarapi.signals import oscarapi_post_checkout

from ocyan.core.utils.cache import RequestCache
from ocyan.main.loading import get_ocyan_class
from ocyan.plugin.oscar.pipeline import (
    PIPELINE_STATUS_CANCELLED,
    PIPELINE_STATUS_NEW,
    PIPELINE_STATUS_PAID,
    PIPELINE_STATUS_PENDING_PAYMENT,
)

from ocyan.plugin.oscar_checkout.checkoutapi.serializers import PriceSerializer
from ocyan.plugin.oscar_checkout.utils import country_from_url

from .signals import newsletter_subscription

logger = logging.getLogger(__name__)

CheckoutSerializer = get_ocyan_class("checkoutapi.serializers", "CheckoutSerializer")
(
    payment_method_choices,
    get_available_shipping_methods,
    get_default_shipping_method,
) = get_classes(
    "checkout.utils",
    [
        "payment_method_choices",
        "get_available_shipping_methods",
        "get_default_shipping_method",
    ],
)

RegisterUserMixin = get_class("customer.mixins", "RegisterUserMixin")
ShippingAddressForm, ShippingMethodForm, GatewayForm = get_classes(
    "checkout.forms", ["ShippingAddressForm", "ShippingMethodForm", "GatewayForm"]
)
BillingAddressForm = get_class("payment.forms", "BillingAddressForm")
UserAddressForm = get_class("address.forms", "UserAddressForm")
PaymentMethodForm = get_class("checkout.forms", "PaymentMethodForm")
ChooseBillingAddressForm = get_class("checkout.forms", "ChooseBillingAddressForm")
ChooseShippingAddressForm = get_class("checkout.forms", "ChooseShippingAddressForm")
SelectBillingAddressForm = get_class("checkout.forms", "SelectBillingAddressForm")
PasswordCheckForm = get_class("checkout.forms", "PasswordCheckForm")
OrderTotalCalculator = get_class("checkout.calculators", "OrderTotalCalculator")
SurchargeApplicator = get_class("checkout.applicator", "SurchargeApplicator")
EventHandler = get_class("order.processing", "EventHandler")

Country = get_model("address", "Country")
UserAddress = get_model("address", "UserAddress")
Order = get_model("order", "Order")
Surcharge = get_model("order", "Surcharge")

BasketVoucherForm = get_class("basket.forms", "BasketVoucherForm")


ORDER_SESSION_KEY = "checkout_order_id"


class IndexView(RegisterUserMixin, TemplateView):
    KEY_FORMS = ["shipping_address_form", "shipping_method_form", "payment_method_form"]
    "These form are the ones that are REQUIRED to be able to fill an order"

    # A pre-condition is a condition that MUST be met in order for a view
    # to be available. If it isn't then the customer should be redirected
    # to a view *earlier* in the chain.
    # pre_conditions is a list of method names that get executed before the
    # normal flow of the view. Each method should check some condition has been
    # met. If not, then an exception is raised that indicates the URL the
    # customer will be redirected to.

    pre_conditions = ["check_basket_is_valid"]

    template_name = "checkout/onestep.html"

    def dispatch(self, request, *args, **kwargs):
        "Make sure that this view can correctly resolve urls for checkoutapi"
        # Enforce any pre-conditions for the view.
        try:
            self.check_pre_conditions(request)
        except FailedPreCondition as e:
            return http.HttpResponseRedirect(e.url)

        if api_settings.DEFAULT_VERSIONING_CLASS is not None:
            versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
            request.version = "checkoutapi"
            request.versioning_scheme = versioning_class()

        return super(IndexView, self).dispatch(request, *args, **kwargs)

    @property
    def key_forms(self):
        return copy.copy(self.KEY_FORMS)

    def get_pre_conditions(self, request):
        """
        Return the pre-condition method names to run for this view
        """
        if self.pre_conditions is None:
            return []
        return self.pre_conditions

    def check_pre_conditions(self, request):
        pre_conditions = self.get_pre_conditions(request)
        for method_name in pre_conditions:
            if not hasattr(self, method_name):
                raise ImproperlyConfigured(
                    "There is no method '%s' to call as a pre-condition" % (method_name)
                )
            getattr(self, method_name)(request)

    def check_basket_is_valid(self, request):
        """
        Check that the basket is permitted to be submitted as an order. That
        is, all the basket lines are available to buy - nothing has gone out of
        stock since it was added to the basket.
        """
        strategy = request.strategy
        for line in request.basket.all_lines().select_related("stockrecord"):
            result = strategy.fetch_for_line(line, stockrecord=line.stockrecord)
            is_permitted = result.availability.is_purchase_permitted(line.quantity)[0]
            if not is_permitted:
                raise FailedPreCondition(url=reverse("basket:summary"))
        RequestCache.cache_for_request(request).clear()

    def forms(self, shipping_methods, shipping_method=None):
        """
        Create all the forms that are used in the template and populate them
        from the POST data if there is any
        """
        data = self.request.POST or None

        return {
            "gateway_form": GatewayForm(self.request, data, prefix="gateway_form"),
            "password_check_form": PasswordCheckForm(data, prefix="gateway_form"),
            "shipping_address_chooser_form": ChooseShippingAddressForm(
                self.request,
                self.get_default_shipping_address(),
                data,
                prefix="shipping_address_chooser",
            ),
            "billing_address_chooser_form": ChooseBillingAddressForm(
                self.request,
                self.get_default_billing_address(),
                data,
                prefix="billing_address_chooser",
            ),
            "shipping_method_form": ShippingMethodForm(
                data,
                methods=shipping_methods,
                default=shipping_method.code if shipping_method is not None else None,
                prefix="shipping_method_form",
            ),
            "shipping_address_form": ShippingAddressForm(
                data,
                initial=self.get_default_shipping_address(),
                prefix="shipping_address_form",
            ),
            "select_billing_address_form": SelectBillingAddressForm(
                data, prefix="select_billing_address_form"
            ),
            "billing_address_form": BillingAddressForm(
                data,
                initial=self.get_default_billing_address(),
                prefix="billing_address_form",
            ),
            "payment_method_form": PaymentMethodForm(
                data,
                prefix="payment_method_form",
                payment_method_choices=self.get_payment_methods(data),
            ),
        }

    def get_default_billing_address(self):
        user = self.request.user
        if user.is_authenticated:
            address = user.addresses.order_by(
                "-is_default_for_billing",
                "-num_orders_as_billing_address",
                "-date_created",
            ).first()
            if address is not None:
                return model_to_dict(address)

        return None

    def get_default_shipping_address(self):
        # Include only addresses where the country is flagged as valid for
        # shipping. Also, use ordering to ensure the default address comes
        # first.
        user = self.request.user
        if user.is_authenticated:
            address = (
                user.addresses.filter(country__is_shipping_country=True)
                .order_by(
                    "-is_default_for_shipping",
                    "-num_orders_as_shipping_address",
                    "-date_created",
                )
                .first()
            )
            if address is not None:
                address_dict = model_to_dict(address)
                if "notes" in address_dict:
                    del address_dict["notes"]
                return address_dict

        return None

    def show_shipping_address_chooser(self):
        if (
            self.request.user.is_authenticated
            and self.request.user.addresses.filter(
                country__is_shipping_country=True
            ).count()
            > 1
        ):
            return True

        return False

    def show_billing_address_chooser(self):
        if (
            self.request.user.is_authenticated
            and self.request.user.addresses.all().count() > 1
        ):
            return True

        return False

    def get_payment_methods(self, data=None):
        user = self.request.user
        basket = self.request.basket

        if data is not None:
            # find payment methods for selected country
            country_code = data.get("shipping_address_form-country")
            if country_code is not None:
                return payment_method_choices(user, basket, country__pk=country_code)
        elif (
            self.request.user.is_authenticated
        ):  # no data? try to fill from existing user address
            default_address = self.get_default_shipping_address()
            if default_address is not None:
                return payment_method_choices(
                    user, basket, country__pk=default_address["country"]
                )

        # get the default country and it's payment choices!
        return payment_method_choices(
            user,
            basket,
            country=Country.objects.filter(is_shipping_country=True).first(),
        )

    def get_context_data(self, shipping_method=None, **kwargs):  # pylint: disable=W0221
        """
        Add the template requirements to the context
        """
        ctx = super(IndexView, self).get_context_data(**kwargs)

        request = self.request
        basket = request.basket
        shipping_address = self.get_default_shipping_address()
        shipping_methods = get_available_shipping_methods(request, shipping_address)
        shipping_method = (
            shipping_method
            if shipping_method is not None
            else get_default_shipping_method(request, shipping_address)
        )
        shipping_charge = shipping_method.calculate(basket)

        ctx.update(self.forms(shipping_methods, shipping_method))
        ctx.update(
            {
                "shipping_methods": shipping_methods,
                "shipping_method": shipping_method,
                "shipping_charge": shipping_charge,
                "basket": basket,
                "voucher_form": BasketVoucherForm(),
                "show_shipping_address_chooser": self.show_shipping_address_chooser(),
                "show_billing_address_chooser": self.show_billing_address_chooser(),
            }
        )
        ctx.update(kwargs)
        surcharges = SurchargeApplicator(self.request, ctx).get_applicable_surcharges(
            basket
        )
        order_total = OrderTotalCalculator().calculate(
            basket, shipping_charge, surcharges
        )
        ctx["order_total"] = order_total
        ctx["surcharges"] = surcharges
        return ctx

    def get_address_data(self, forms, key="shipping_address_form"):
        address_form = forms[key]
        if address_form.is_valid():
            address = address_form.cleaned_data
            address["country"] = reverse(
                "country-detail", args=(address["country"].pk,), request=self.request
            )
            return address

        return None

    def get_shipping_address_data(self, forms):
        return self.get_address_data(forms, "shipping_address_form")

    def get_billing_address_data(self, forms):
        return self.get_address_data(forms, "billing_address_form")

    def build_serializer_data(self, forms):
        """
        Because we use the oscarapi serializer to create the order we have to
        do a little bit of processing to turn the validated form data into
        the serializers data structure.
        """
        request = self.request
        basket = request.basket
        shipping_method_form = forms["shipping_method_form"]
        payment_method_form = forms["payment_method_form"]
        shipping_method = shipping_method_form.method
        shipping_address = self.get_shipping_address_data(forms)

        shipping_country = country_from_url(shipping_address.get("country", None))

        request.META["shipping_country"] = shipping_country

        serializer_data = {
            "basket": reverse("basket-detail", args=(basket.pk,), request=request),
            "shipping_address": shipping_address,
            "shipping_method_code": shipping_method_form.cleaned_data["method_code"],
            "shipping_charge": PriceSerializer(shipping_method.calculate(basket)).data,
            "payment_method": payment_method_form.method,
        }

        if self.require_billing_address(forms):
            billing_address = self.get_billing_address_data(forms)
            if billing_address is not None:
                serializer_data["billing_address"] = billing_address

        if (
            not request.user.is_authenticated
            and forms["gateway_form"].is_guest_checkout()
        ):
            serializer_data["guest_email"] = forms["gateway_form"].cleaned_data[
                "username"
            ]

        surcharges = SurchargeApplicator(
            request, serializer_data
        ).get_applicable_surcharges(basket)

        serializer_data["surcharges"] = surcharges

        return serializer_data

    def require_billing_address(self, forms):
        select_billing_address_form = forms["select_billing_address_form"]
        return (
            select_billing_address_form.is_valid()
            and select_billing_address_form.cleaned_data["require_billing_address"]
        )

    def key_forms_valid(self, forms):
        """
        Returns ``True`` if all of the forms required for the order are valid.
        """
        key_forms = self.key_forms
        if self.require_billing_address(forms):
            key_forms.append("billing_address_form")

        select_key_forms = operator.itemgetter(*key_forms)

        # If one of the key forms is invalid, return False
        return not any([not x.is_valid() for x in select_key_forms(forms)])

    def handle_gateway_form(self, gateway_form, password_check_form):
        """
        Register or log in user as specified by the options input.
        """
        gateway_form_valid = gateway_form.is_valid()

        if self.request.user.is_authenticated:
            return True, False
        elif not gateway_form_valid:
            return False, False

        if gateway_form.is_guest_checkout():
            return True, False
        elif gateway_form.is_new_account_checkout():
            if not password_check_form.is_valid():
                return False, False

            email = gateway_form.cleaned_data["username"]
            create_form = password_check_form.user_create_form(email)
            if create_form is None:
                return False, False
            # since the user is new, it is safe to assign the anonymous
            # basket to the new user without creating a new basket.
            self.request.basket.owner = self.register_user(create_form)
            self.request.basket.save()
            return True, False
        else:
            user = gateway_form.get_user()
            login(self.request, user)
            return True, True

    def handle_newsletter_subscription(self, POST, order, user):
        if not feature_hidden("newsletter") and POST.get("subscribetonewsletter"):
            try:
                full_name = order.shipping_address.name
            except AttributeError:
                if user.is_authenticated:
                    full_name = user.get_full_name()
                else:
                    full_name = "unknown"

            newsletter_subscription.send(
                sender=order.__class__, full_name=full_name, email=order.email
            )

    def handle_terms_of_service(self, request, gateway_passed):
        if not request.POST.get("accepttermsofservice"):
            gateway_passed = False
            messages.error(request, gettext("Please accept our terms of service."))

        return gateway_passed

    def get_posted_shipping_address(self, request):
        """
        Get the posted shipping adddres before validating all the forms.
        """
        forms = self.forms([])
        shipping_address_form = forms["shipping_address_form"]
        if shipping_address_form.is_valid():
            return shipping_address_form.cleaned_data
        else:
            return shipping_address_form.cleaned_data

    def get(self, request, *args, **kwargs):
        if request.basket.is_empty and ORDER_SESSION_KEY in request.session:
            order = Order.objects.get(pk=request.session[ORDER_SESSION_KEY])
            if order.status in [PIPELINE_STATUS_NEW, PIPELINE_STATUS_PENDING_PAYMENT]:
                messages.error(
                    request,
                    gettext(
                        "Something went wrong while placing your order, if you haven't paid yet, you can do so on this page. In case you did make payment, please contact our customer service for assistance."
                    ),
                )
                return redirect("checkout:try-again")

        return super().get(request, *args, **kwargs)

    def post(self, request):
        """
        Handle the creation of the order.
        """
        request.META["shipping_country"] = request.POST.get(
            "shipping_address_form-country"
        )
        shipping_address = self.get_posted_shipping_address(request)
        shipping_methods = get_available_shipping_methods(request, shipping_address)

        basket = request.basket
        context = {}

        forms = self.forms(shipping_methods)
        gateway_passed, user_logged_in = self.handle_gateway_form(
            forms["gateway_form"], forms["password_check_form"]
        )

        # if we login using gateway_form redirect to checkout index.
        if user_logged_in:
            return redirect("checkout:index")

        # Handle edge cases where something happened to the connection while
        # placing an order, in that case the basket should be empty and
        # a ORDER_SESSION_KEY should be found in the session
        if basket.is_empty and ORDER_SESSION_KEY in request.session:
            order = Order.objects.get(pk=request.session[ORDER_SESSION_KEY])
            if order.status == PIPELINE_STATUS_NEW:
                # if the status of the order is PIPELINE_STATUS_NEW and the
                # payment_method info was posted again for the second time by
                # the client, we can just redirect to the correct payment method.
                if forms["payment_method_form"].is_valid():
                    return redirect(forms["payment_method_form"].method)

            # if the order had one of these 2 statusses the client can retry
            # payment on the try again view.
            if order.status in [PIPELINE_STATUS_NEW, PIPELINE_STATUS_PENDING_PAYMENT]:
                messages.error(
                    request,
                    gettext(
                        "Something went wrong while placing your order, if you haven't paid yet, you can do so on this page. In case you did make payment, please contact our customer service for assistance."
                    ),
                )
                return redirect("checkout:try-again")

            if order.status == PIPELINE_STATUS_PAID:
                return redirect("checkout:thank-you")

            # if we got here, there is nothing wrong so let's ignore.

        gateway_passed = self.handle_terms_of_service(request, gateway_passed)

        if self.key_forms_valid(forms) and gateway_passed:
            serializer_data = self.build_serializer_data(forms)
            ser = CheckoutSerializer(data=serializer_data, context={"request": request})

            if ser.is_valid():
                basket.freeze()
                order = ser.save()
                oscarapi_post_checkout.send(
                    sender=self,
                    order=order,
                    user=request.user,
                    request=request,
                    response=response,
                )
                self.handle_newsletter_subscription(request.POST, order, request.user)
                request.session[ORDER_SESSION_KEY] = order.id
                return redirect(forms["payment_method_form"].method)
            else:
                raise ValidationError(
                    "Serializer populated from validated form has errors %s"
                    % ser.errors
                )
        else:
            shipping_method_form = forms["shipping_method_form"]
            if shipping_method_form.is_valid():
                context["shipping_method"] = shipping_method_form.method

        for _, form in forms.items():
            for error in form.non_field_errors():
                messages.error(request, error)

        context.update(forms)
        context = self.get_context_data(**context)
        return self.render_to_response(context)


class ThankYouView(BaseThankYouView):
    def get_object(self, *args, **kwargs):
        order = super().get_object(*args, **kwargs)
        if order is not None:
            activate(order.language_code)
        return order


class ThankYouFromLinkView(ThankYouView):
    def get(self, request, oidb64, token):  # pylint: disable=W0221
        self.object = self.get_object(oidb64, token)  # pylint: disable=W0201
        if self.object is None:
            return redirect("checkout:order-not-found")

        return self.render_to_response(self.get_context_data(order=self.object))

    def get_object(self, oidb64, token):  # pylint: disable=W0221
        try:
            order_id = urlsafe_base64_decode(oidb64).decode()
        except UnicodeDecodeError:
            return None

        order = Order.objects.filter(pk=order_id).first()
        if order is None:
            return None

        if not order.check_verification_hash(token):
            return None

        return order


class OrderNotFoundView(TemplateView):
    template_name = "checkout/order_not_found.html"


class CancelView(ThankYouView):
    template_name = "checkout/cancel.html"

    def get(self, request, *args, **kwargs):
        order = self.get_object()
        if order.status == PIPELINE_STATUS_CANCELLED:
            request.basket.save()  # basket could be newly created, save first.
            request.basket.merge(order.basket)

        return super(CancelView, self).get(request, *args, **kwargs)


class TryAgainView(TemplateView):
    template_name = "checkout/try_again.html"

    def get(self, request, *args, **kwargs):
        if (
            self.order.status == PIPELINE_STATUS_PENDING_PAYMENT
            or self.order.status == PIPELINE_STATUS_NEW
        ):
            # Check if javascript request for partial update regarding totals
            if request.headers.get("X-Requested-With") == "XMLHttpRequest":
                return self.render_totals(request, *args, **kwargs)

            return super().get(request, *args, **kwargs)
        else:
            raise Http404

    def post(self, request, *args, **kwargs):
        data = request.POST or None
        form = self.payment_method_form(data)
        event_handler = EventHandler(user=request.user)

        if request.POST.get("_cancel"):
            event_handler.handle_order_status_change(
                self.order, PIPELINE_STATUS_CANCELLED
            )
            return redirect("checkout:cancel")

        if request.POST.get("_try-again"):
            if form.is_valid():
                # The order could have changed (eg; surcharges from the chosen payment method),
                # make sure to save it before redirecting to the payment gateway (save=True).
                self.get_recalculated_order_obj(
                    surcharges=self.get_surcharges(form.cleaned_data["payment_method"]),
                    save=True,
                )
                return redirect(form.method)
            else:
                messages.error(request, gettext("Select a payment method"))
                return render(
                    request, self.template_name, context=self.get_context_data()
                )

        # Invalid POST request, return 404.
        raise Http404

    def render_totals(self, request, *args, **kwargs):
        payment_method = request.GET.get("payment_method", "")
        context = self.get_order_context(payment_method)
        context.update(
            {
                "nohide": True,
                "title": gettext("Your order"),
                "id": "checkouttotal",
            }
        )
        return render(request, "checkout/partials/try_again_totals_block.html", context)

    def get_context_data(self, *args, **kwargs):
        ctx = super().get_context_data(*args, **kwargs)
        ctx["payment_method_form"] = self.payment_method_form()
        # make sure nobody can mess with the basket
        ctx["basket"] = None
        ctx.update(self.get_order_context())
        return ctx

    def get_order_context(self, payment_method=""):
        surcharges = self.get_surcharges(payment_method)
        recalculated_order = self.get_recalculated_order_obj(surcharges)
        return {
            "order": recalculated_order,
            "order_total": Price(
                currency=recalculated_order.currency,
                excl_tax=recalculated_order.total_excl_tax,
                incl_tax=recalculated_order.total_incl_tax,
            ),
            "surcharges": surcharges,
        }

    def payment_method_form(self, data=None):
        user = self.request.user
        choices = payment_method_choices(
            user, self.order, country=self.order.shipping_address.country
        )
        return PaymentMethodForm(data, payment_method_choices=choices)

    @cached_property
    def order(self):
        if ORDER_SESSION_KEY in self.request.session:
            order = get_object_or_404(
                Order, pk=self.request.session[ORDER_SESSION_KEY], basket__isnull=False
            )
            order.basket.strategy = self.request.basket.strategy
            return order

        raise Http404(gettext("No order found"))

    @cached_property
    def order_shipping_charge(self):
        return Price(
            currency=self.order.currency,
            excl_tax=self.order.shipping_excl_tax,
            incl_tax=self.order.shipping_incl_tax,
        )

    def get_try_again_basket(self):
        """
        Returns a basket that's compatible with the TryAgain logic. It returns
        the order's basket totals, instead of the total of the basket itself. The basket
        itself recalculates all lines, and for now, we don't implement this as this would
        require updating A LOT of fields, related models (line price, line discounts etc)
        and all of that has to be done without losing the original order.
        """

        class TryAgainCompatibleBasket:
            def __init__(self, basket, order):
                self.basket = basket
                self.order = order

            def __getattr__(self, name):
                return getattr(self.basket, name)

            @property
            def total_excl_tax(self):
                return self.order.basket_total_excl_tax

            @property
            def total_incl_tax(self):
                return self.order.basket_total_incl_tax

        return TryAgainCompatibleBasket(self.order.basket, self.order)

    def get_surcharges(self, payment_method=""):
        context = {
            "payment_method": payment_method,
            "shipping_address": self.order.shipping_address.__dict__,
        }
        return SurchargeApplicator(self.request, context).get_applicable_surcharges(
            self.get_try_again_basket(), shipping_charge=self.order_shipping_charge
        )

    def recalculate_order_total(self, surcharges=None):
        """
        This method recalculates the order total based on the NEW surcharges.
        We can NOT use the OrderTotalCalculator, as this also re-calculates the
        basket lines, which we don't want.
        """
        total_without_surcharge = Price(
            currency=self.order.currency,
            excl_tax=self.order.total_excl_tax - self.order.surcharge_excl_tax,
            incl_tax=self.order.total_incl_tax - self.order.surcharge_incl_tax,
        )

        if surcharges:
            total_without_surcharge += surcharges.total

        return total_without_surcharge

    def get_recalculated_order_obj(self, surcharges, save=False):
        new_order_total = self.recalculate_order_total(surcharges)

        self.order.total_incl_tax = new_order_total.incl_tax
        self.order.total_excl_tax = new_order_total.excl_tax

        # If save has been set to True, we should save the order with the new totals.
        # We should also create surcharge models for the order.
        if save:
            with transaction.atomic():
                # First, delete the old surcharges
                self.order.surcharges.all().delete()

                # Then, create the new surcharges
                if surcharges:
                    for charge in surcharges:
                        surcharge = Surcharge(
                            order=self.order,
                            name=charge.surcharge.name,
                            code=charge.surcharge.code,
                            excl_tax=charge.price.excl_tax,
                            incl_tax=charge.price.incl_tax,
                        )
                        # check if the surcharge model has a 'tax_code' field (compatibility with older versions of Oscar)
                        if hasattr(surcharge, "tax_code"):
                            surcharge.tax_code = charge.price.tax_code

                        surcharge.save()

                self.order.save()

        return self.order


class TryAgainFromLinkView(UserPassesTestMixin, TryAgainView):
    template_name = "checkout/try_again_from_link.html"

    def test_func(self):
        order = self.order

        if order.is_anonymous:
            if order.check_verification_hash(self.kwargs["token"]):
                return True

            # pretend this page does not exist if verification_hash check
            # failed
            raise Http404()

        if not self.request.user.is_authenticated:
            # redirect to login if user is nog logged in and order is
            # not anynymous
            return False

        if order.user is not None and self.request.user != order.user:
            # pretend page does not exist if user tries to access order
            # from a different user
            raise Http404()

        if not order.check_verification_hash(self.kwargs["token"]):
            # if the token is not valid, pretend the page does not exist
            raise Http404()

        return True

    @cached_property
    def order(self):
        order_id = urlsafe_base64_decode(self.kwargs["oidb64"]).decode()
        order = get_object_or_404(Order, pk=order_id, basket__isnull=False)
        order.basket.strategy = self.request.basket.strategy
        return order

    def post(self, request, *args, **kwargs):
        resp = super().post(request, *args, **kwargs)
        try:
            if resp.status_code == 302:
                if not request.POST.get("_cancel"):
                    request.session[ORDER_SESSION_KEY] = self.order.pk
        except (AttributeError, IndexError):
            pass

        return resp
