import logging
import requests
import json
import stripe
from django.conf import settings
from django.utils import timezone
from django.shortcuts import render, reverse, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.core.mail import EmailMessage
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.http import JsonResponse, HttpResponseRedirect
from django.views.generic.base import View, RedirectView, TemplateView
from django.views.generic.edit import (
    FormView, CreateView, UpdateView, DeleteView, FormMixin
)
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.list import ListView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib import messages
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.forms.models import model_to_dict

from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment

from accounts.models import User, Address
from accounts.utils import get_or_create_customer
from accounts.forms import (
    AddressForm as AccountAddressForm, CustomerUpdateForm
)
from core.models import Product, Order, Transaction, OrderLine, Coupon
from core.forms import ShippingMethodForm
from core import OrderStatus, ShippingContainer

from .forms import (
    AddToCartForm, UpdateCartItemForm, OrderCreateForm,
    AddressForm, CouponApplyForm, ContactForm, CheckoutShippingForm,
    SubscriptionCreateForm
)
from .cart import Cart
from .payments import CaptureOrder

logger = logging.getLogger(__name__)


class CartView(TemplateView):
    template_name = 'storefront/cart_detail.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        cart = Cart(self.request)
        for item in cart:
            for variation in item['variations'].values():
                variation['update_quantity_form'] = UpdateCartItemForm(
                    initial={
                        'quantity': variation['quantity']
                    }
                )
        context['cart'] = cart
        context['coupon_apply_form'] = CouponApplyForm()
        return context


class CartAddProductView(SingleObjectMixin, FormView):
    model = Product
    form_class = AddToCartForm
    http_method_names = ['post']

    def get_success_url(self):
        return reverse('storefront:cart-detail')

    def post(self, request, *args, **kwargs):
        cart = Cart(request)
        form = self.get_form()
        if form.is_valid():
            cart.add(
                request=request,
                product=self.get_object(),
                grind=form.cleaned_data['grind'],
                quantity=form.cleaned_data['quantity']
            )
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        return super().form_valid(form)


class CartUpdateProductView(SingleObjectMixin, FormView):
    model = Product
    form_class = UpdateCartItemForm
    http_method_names = ['post']

    def get_success_url(self):
        return reverse('storefront:cart-detail')

    def post(self, request, *args, **kwargs):
        cart = Cart(request)
        form = self.get_form()
        if form.is_valid():
            cart.add(
                request=request,
                product=self.get_object(),
                grind=kwargs['grind'],
                quantity=form.cleaned_data['quantity'],
                update_quantity=form.cleaned_data['update']
            )
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        return super().form_valid(form)


def cart_remove_product_view(request, pk, grind):
    cart = Cart(request)
    product = get_object_or_404(Product, id=pk)
    cart.remove(product, grind)
    return redirect('storefront:cart-detail')


class CouponApplyView(FormView):
    form_class = CouponApplyForm
    success_url = reverse_lazy('storefront:cart-detail')
    http_method_names = ['post']

    def form_valid(self, form):
        today = timezone.localtime(timezone.now()).date()
        code = form.cleaned_data['code'].upper()
        try:
            coupon = Coupon.objects.get(
                code__iexact=code,
                valid_from__date__lte=today,
                valid_to__date__gte=today
            )
            if coupon.is_valid:
                self.request.session['coupon_code'] = coupon.code
        except ObjectDoesNotExist:
            self.request.session['coupon_code'] = None
        return super().form_valid(form)


class ProductListView(FormMixin, ListView):
    model = Product
    template_name = 'storefront/product_list.html'
    form_class = AddToCartForm
    ordering = 'sorting'

    queryset = Product.objects.filter(
        visible_in_listings=True
    )


class ProductDetailView(FormMixin, DetailView):
    model = Product
    template_name = 'storefront/product_detail.html'
    form_class = AddToCartForm


class CheckoutAddressView(FormView):
    template_name = 'storefront/checkout_address.html'
    form_class = AddressForm
    success_url = reverse_lazy('storefront:checkout-shipping')

    def get_initial(self):
        user = self.request.user
        initial = None
        if user.is_authenticated and user.default_shipping_address:
            address = user.default_shipping_address
            initial = {
                'full_name': address.first_name+' '+address.last_name,
                'email': user.email,
                'street_address_1': address.street_address_1,
                'street_address_2': address.street_address_2,
                'city': address.city,
                'state': address.state,
                'postal_code': address.postal_code
            }
        elif self.request.session.get('shipping_address'):
            address = self.request.session.get('shipping_address')
            initial = {
                'full_name': address['first_name']+' '+address['last_name'],
                'email': address['email'],
                'street_address_1': address['street_address_1'],
                'street_address_2': address['street_address_2'],
                'city': address['city'],
                'state': address['state'],
                'postal_code': address['postal_code']
            }
        return initial

    def form_valid(self, form):
        # save address data to session
        cleaned_data = form.cleaned_data
        first_name, last_name = form.process_full_name(
            cleaned_data.get('full_name')
        )
        address = {
            'first_name': first_name,
            'last_name': last_name,
            'email': cleaned_data['email'],
            'street_address_1': cleaned_data['street_address_1'],
            'street_address_2': cleaned_data['street_address_2'],
            'city': cleaned_data['city'],
            'state': cleaned_data['state'],
            'postal_code': cleaned_data['postal_code']
        }
        self.request.session['shipping_address'] = address
        return super().form_valid(form)


class CheckoutShippingView(FormView):
    template_name = 'storefront/checkout_shipping_form.html'
    form_class = CheckoutShippingForm
    success_url = reverse_lazy('storefront:order-create')

    def get(self, request, *args, **kwargs):
        cart = Cart(request)
        if len(cart) != 6:
            if 'shipping_container' in self.request.session:
                del self.request.session['shipping_container']
            return HttpResponseRedirect(
                reverse('storefront:order-create')
            )

        if not self.request.session.get("shipping_address"):
            messages.warning(request, 'Please add a shipping address.')
            return HttpResponseRedirect(
                reverse('storefront:checkout-address')
            )

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

    def get_context_data(self, **kwargs):
        cart = Cart(self.request)
        context = super().get_context_data(**kwargs)
        context['MD_FLAT_RATE_BOX'] = cart.get_shipping_cost(
            ShippingContainer.MD_FLAT_RATE_BOX
        )
        context['REGIONAL_RATE_BOX_B'] = cart.get_shipping_cost(
            ShippingContainer.REGIONAL_RATE_BOX_B
        )
        return context

    def form_valid(self, form):
        cleaned_data = form.cleaned_data
        self.request.session['shipping_container'] = cleaned_data.get(
            'shipping_method'
        )
        return super().form_valid(form)


class OrderCreateView(CreateView):
    model = Order
    template_name = 'storefront/order_form.html'
    form_class = OrderCreateForm
    success_url = reverse_lazy('storefront:payment-done')

    def get(self, request, *args, **kwargs):
        cart = Cart(request)
        if len(cart) != 6 and 'shipping_container' in self.request.session:
            del self.request.session['shipping_container']

        if not self.request.session.get("shipping_address"):
            messages.warning(request, 'Please add a shipping address.')
            return HttpResponseRedirect(
                reverse('storefront:checkout-address')
            )
        elif self.request.session.get('coupon_code'):
            address = self.request.session.get("shipping_address")
            coupon = Coupon.objects.get(
                code=self.request.session.get('coupon_code')
            )
            try:
                user = User.objects.get(email=address['email'])
            except ObjectDoesNotExist:
                user = None
            if user in coupon.users.all():
                del self.request.session['coupon_code']
                messages.warning(request, 'Coupon already used.')

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

    def get_initial(self):
        cart = Cart(self.request)
        try:
            shipping_cost = cart.get_shipping_cost()
        except Exception as e:
            raise e('Could not get shipping information')
            shipping_cost = Decimal('0.00')

        initial = {
            'total_net_amount': cart.get_total_price(),
            'shipping_total': shipping_cost
        }
        if self.request.session.get('shipping_address'):
            a = self.request.session.get('shipping_address')
            user_info = {
                'email': a['email'],
                'first_name': a['first_name'],
                'last_name': a['last_name']
            }
            initial |= user_info

        return initial

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['shipping_address'] = self.request.session.get('shipping_address')
        context['PAYPAL_CLIENT_ID'] = settings.PAYPAL_CLIENT_ID
        return context

    def form_valid(self, form):
        cart = Cart(self.request)
        shipping_address = self.request.session.get('shipping_address')
        shipping_container = self.request.session.get('shipping_container')
        form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)
        form.instance.status = OrderStatus.DRAFT
        self.object = form.save()
        bulk_list = cart.build_bulk_list(self.object)
        objs = OrderLine.objects.bulk_create(bulk_list)

        response = cart.create_order(shipping_container)
        data = response.result.__dict__['_dict']

        self.request.session['order_id'] = self.object.pk

        return JsonResponse(data)


@csrf_exempt
@require_POST
def paypal_order_transaction_capture(request, transaction_id):
    if request.method == "POST":
        data = CaptureOrder().capture_order(transaction_id)
        cart = Cart(request)
        order = Order.objects.get(pk=request.session.get('order_id'))
        order.status = OrderStatus.UNFULFILLED
        try:
            coupon = Coupon.objects.get(
                    code=request.session.get('coupon_code')
                )
        except ObjectDoesNotExist:
            coupon = None

        if coupon:
            order.coupon = coupon
            coupon.users.add(order.customer)
        order.save()
        transaction = Transaction.objects.get(order=order)
        transaction.paypal_id = data['purchase_units'][0]['payments']['captures'][0]['id']
        transaction.status = data['status']
        transaction.save()
        cart.clear()
        logger.debug(f'\nPayPal Response data: {data}\n')

        return JsonResponse(data)
    else:
        return JsonResponse({'details': 'invalid request'})


@csrf_exempt
@require_POST
def paypal_webhook_endpoint(request):
    data = json.loads(request.body)
    logger.info(data)
    return JsonResponse(data)


class PaymentDoneView(TemplateView):
    template_name = 'storefront/payment_done.html'


class PaymentCanceledView(TemplateView):
    template_name = 'storefront/payment_canceled.html'


class CustomerDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
    model = User
    template_name = 'storefront/customer_detail.html'
    context_object_name = 'customer'
    permission_denied_message = 'Not authorized.'
    raise_exception = True

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['order_list'] = Order.objects.without_drafts().filter(
            customer=self.object
        ).prefetch_related('lines')
        return context

    def test_func(self):
        return self.request.user.pk == self.get_object().pk


class CustomerUpdateView(UserPassesTestMixin, LoginRequiredMixin, UpdateView):
    model = User
    template_name = 'storefront/customer_form.html'
    context_object_name = 'customer'
    form_class = CustomerUpdateForm
    permission_denied_message = 'Not authorized.'
    raise_exception = True

    def test_func(self):
        return self.request.user.pk == self.get_object().pk

    def get_success_url(self):
        return reverse(
            'storefront:customer-detail', kwargs={'pk': self.object.pk}
        )


class OrderDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
    model = Order
    template_name = 'storefront/order_detail.html'
    pk_url_kwarg = 'order_pk'
    permission_denied_message = 'Not authorized.'
    raise_exception = True

    def test_func(self):
        return self.request.user.pk == self.get_object().customer.pk

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['customer'] = User.objects.get(pk=self.kwargs['pk'])
        return context


class CustomerAddressCreateView(
    UserPassesTestMixin, LoginRequiredMixin, CreateView
):
    model = Address
    template_name = 'storefront/address_create_form.html'
    form_class = AccountAddressForm
    permission_denied_message = 'Not authorized.'
    raise_exception = True

    def test_func(self):
        return self.request.user.pk == self.kwargs['pk']

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['customer'] = User.objects.get(pk=self.kwargs['pk'])
        return context

    def form_valid(self, form):
        customer = User.objects.get(pk=self.kwargs['pk'])
        self.object = form.save()
        customer.addresses.add(self.object)

        return super().form_valid(form)

    def get_success_url(self):
        return reverse(
            'storefront:customer-detail', kwargs={'pk': self.kwargs['pk']}
        )


class CustomerAddressUpdateView(
    UserPassesTestMixin, LoginRequiredMixin, UpdateView
):
    model = Address
    pk_url_kwarg = 'address_pk'
    template_name = 'storefront/address_form.html'
    form_class = AccountAddressForm
    permission_denied_message = 'Not authorized.'
    raise_exception = True

    def test_func(self):
        return self.request.user.pk == self.kwargs['pk']

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['customer'] = User.objects.get(pk=self.kwargs['pk'])
        return context

    def get_success_url(self):
        return reverse('storefront:customer-detail', kwargs={'pk': self.kwargs['pk']})


class AboutView(TemplateView):
    template_name = 'storefront/about.html'


class FairTradeView(TemplateView):
    template_name = 'storefront/fairtrade.html'


class ReviewListView(TemplateView):
    template_name = 'storefront/reviews.html'


class ContactFormView(FormView, SuccessMessageMixin):
    template_name = 'storefront/contact_form.html'
    form_class = ContactForm
    success_url = reverse_lazy('storefront:product-list')
    success_message = 'Message sent.'

    def form_valid(self, form):
        form.send_email()
        return super().form_valid(form)


class SubscriptionCreateView(FormView):
    template_name = 'storefront/subscriptions.html'
    form_class = SubscriptionCreateForm
    success_url = reverse_lazy('storefront:payment-done')

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['STRIPE_API_KEY'] = settings.STRIPE_API_KEY
        context['product_list'] = Product.objects.filter(
            visible_in_listings=True
        )
        return context


class CreatePayment(View):
    def post(self, request, *args, **kwargs):
        stripe.api_key = settings.STRIPE_API_KEY
        intent = stripe.PaymentIntent.create(
            amount=2000,
            currency=settings.DEFAULT_CURRENCY,
            automatic_payment_methods={
                'enabled': True,
            },
        )
        return JsonResponse({
            'clientSecret': intent['client_secret']
        })
