import logging
import json
import decimal 
from stripe.error import CardError
from django.shortcuts import redirect, render
from django.contrib import messages
from django.utils.translation import get_language, ugettext_lazy as _
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import cache_control
from django.utils.decorators import method_decorator
from django.views import View
from django.views.generic import FormView, DetailView
from django.views.generic.list import ListView
from matrixhosting.forms import InitialRequestForm, BillingAddressForm, RequestDomainsNamesForm
from django.urls import reverse
from django.conf import settings
from django.http import (
    HttpResponseRedirect, JsonResponse, HttpResponse
)
from wkhtmltopdf.views import PDFTemplateResponse
from rest_framework import viewsets, permissions

from uncloud_pay.models import PricingPlan
from uncloud_pay.utils import get_order_total_with_vat
from uncloud_pay.models import *
from uncloud_pay.utils import validate_vat_number
from uncloud_pay.selectors import get_billing_address_for_user, has_enough_balance, get_balance_for_user
import uncloud_pay.stripe as uncloud_stripe
from .models import VMInstance
from .serializers import *
from .utils import *

logger = logging.getLogger(__name__)

class IndexView(FormView):
    template_name = "matrixhosting/index.html"
    form_class = InitialRequestForm
    success_url = "/matrixhosting#requestform"
    success_message = "Thank you, we will contact you as soon as possible"


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['matrix_vm_pricing'] = PricingPlan.get_default_pricing()
        return context
    
    def form_valid(self, form):
        self.request.session['order'] = form.cleaned_data
        pricing = get_order_total_with_vat(
            form.cleaned_data['cores'],
            form.cleaned_data['memory'],
            form.cleaned_data['storage'],
            form.cleaned_data['pricing_name'], 
            False
        )
        self.request.session['pricing'] = pricing
        return HttpResponseRedirect(reverse('matrix:payment'))


class OrderPaymentView(FormView):
    template_name = 'matrixhosting/order_details.html'
    success_url = 'matrix:order_confirmation'
    form_class = BillingAddressForm

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(OrderPaymentView, self).get_context_data(**kwargs)
        if 'billing_address_data' in self.request.session:
            billing_address_form = BillingAddressForm(
                    initial=self.request.session['billing_address_data']
                )
        else:
            old_active = get_billing_address_for_user(self.request.user)
            billing_address_form = BillingAddressForm(
                    instance=old_active
                ) if old_active else BillingAddressForm(
                    initial={'active': True, 'owner': self.request.user.id}
                )
        details_form = InitialRequestForm(
                initial=self.request.session.get('order', {})
            )
        balance = get_balance_for_user(self.request.user)
        customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
        #TODO optimize this part for better performance
        uncloud_stripe.sync_cards_for_user(self.request.user)
        cards = uncloud_stripe.get_customer_cards(customer_id)
        context.update({
            'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']), 
            'billing_address_form': billing_address_form,
            'details_form': details_form,
            'balance': balance,
            'cards': cards,
            'stripe_key': settings.STRIPE_PUBLIC_KEY,
            'show_cards': True if balance < self.request.session.get('pricing')['total'] else False,
        })
        return context

    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
    def get(self, request, *args, **kwargs):
        for k in ['vat_validation_status', 'token', 'id_payment_method', 'total']:
            if request.session.get(k):
                request.session.pop(k)
        if 'order' not in request.session:
            return HttpResponseRedirect(reverse('matrix:index'))
        return self.render_to_response(self.get_context_data())
    
    def post(self, request, *args, **kwargs):
        details_form = InitialRequestForm(request.POST)
        billing_address_form = BillingAddressForm(request.POST)
        if not details_form.is_valid() or not billing_address_form.is_valid():
            context = self.get_context_data()
            context.update({'details_form': details_form, 
                            'billing_address_form': billing_address_form})
            return self.render_to_response(context)
        address = get_billing_address_for_user(self.request.user)
        if address:
            form = BillingAddressForm(self.request.POST, instance=address)
        else:
            form = BillingAddressForm(self.request.POST)
        if form.is_valid:
            billing_address_ins = form.save()
            self.request.session["billing_address_id"] = billing_address_ins.id
            self.request.session['billing_address_data'] = billing_address_form.cleaned_data
            self.request.session['billing_address_data']['owner'] = self.request.user.id
        id_payment_method = self.request.POST.get('id_payment_method', False)
        selected_card = False
        if id_payment_method and id_payment_method != 'undefined':
            uncloud_stripe.attach_payment_method(id_payment_method, self.request.user)
            selected_card = StripeCreditCard.objects.filter(card_id=id_payment_method).first()
            selected_card.activate()
        vat_number = billing_address_form.cleaned_data.get('vat_number').strip()
        if vat_number:
            customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
            validate_result = validate_vat_number(
                stripe_customer_id=customer_id,
                billing_address_id=billing_address_ins.id
            )
            if 'error' in validate_result and validate_result['error']:
                messages.add_message(
                    self.request, messages.ERROR, validate_result["error"],
                    extra_tags='error'
                )
                return HttpResponseRedirect(
                    reverse('matrix:payment')
                )
            self.request.session["vat_validation_status"] = validate_result["status"]
        specs = details_form.cleaned_data
        vat_rate = VATRate.get_vat_rate(billing_address_ins)
        vat_validation_status = "verified" if billing_address_ins.vat_number_validated_on and billing_address_ins.vat_number_verified else False
        pricing = get_order_total_with_vat(
            specs['cores'], specs['memory'], specs['storage'], request.session['pricing']['name'],
            vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
        )
        self.request.session['pricing'] = pricing
        self.request.session['order'] = specs
        self.request.session['vat_validation_status'] = vat_validation_status
        amount = get_balance_for_user(self.request.user) - decimal.Decimal(pricing["total"]) 
        if (amount < 0 and not selected_card):
            messages.add_message(
                    self.request, messages.ERROR, "You haven't enough balance please select credit card to continue",
                    extra_tags='error'
            )
            return HttpResponseRedirect(
                    reverse('matrix:payment')
                )
        return HttpResponseRedirect(reverse('matrix:order_confirmation'))

class OrderConfirmationView(DetailView):
    template_name = "matrixhosting/order_confirmation.html"
    context_object_name = "order"
    model = Order

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = {
             'order': self.request.session.get('order'),
            'pricing': self.request.session.get('pricing'),
            'balance': get_balance_for_user(self.request.user)
        }
        return context

    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
    def get(self, request, *args, **kwargs):
        context = self.get_context_data()
        context['domains_form'] = RequestDomainsNamesForm(initial={})
        if ('order' not in request.session):
            return HttpResponseRedirect(reverse('matrix:index'))
        elif 'pricing' not in self.request.session or 'vat_validation_status' not in self.request.session:
            return HttpResponseRedirect(reverse('matrix:payment')) 

        total = self.request.session['pricing']['total']       
        amount = get_balance_for_user(self.request.user) - decimal.Decimal(total)   
        if (amount < 0):
            context['stripe_deposit_amount'] = max(amount, settings.MIN_PER_TRANSACTION)
        return render(request, self.template_name, context)

    def post(self, request, *args, **kwargs):
        domains_form = RequestDomainsNamesForm(self.request.POST)
        if domains_form.is_valid():
            customer = StripeCustomer.objects.get(owner=self.request.user)
            billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id')) 
            total = self.request.session['pricing']['total']       
            self.request.session['order']['matrix_domain'] = 'ungleich.ch'
            self.request.session['order']['homeserver_domain'] = domains_form.cleaned_data.get('homeserver_name') + ".matrix.ungleich.cloud"
            self.request.session['order']['webclient_domain'] = domains_form.cleaned_data.get('webclient_name') + ".matrix.0co2.cloud"
            self.request.session['order']['is_open_registration'] = domains_form.cleaned_data.get('is_open_registration')
            try:
                amount = get_balance_for_user(self.request.user) - decimal.Decimal(total) 
                if (amount < 0):
                    Payment.deposit(request.user, abs(max(amount, settings.MIN_PER_TRANSACTION)), source='stripe')
                order = finalize_order(request, customer, 
                        billing_address,
                        total, 
                        PricingPlan.get_by_name(self.request.session['pricing']['name']),
                        request.session.get('order'))
                if order:
                    bill = Bill.create_next_bill_for_order(order)
                    self.request.session['bill_id'] = bill.id
                    payment= Payment.withdraw(owner=request.user, amount=total, notes=f"BILL #{bill.id}")
                    if payment:
                        #Close the bill as the payment has been added
                        bill.close()
                    return HttpResponseRedirect(reverse('matrix:order_success'))   
            except CardError as e:
                messages.add_message(
                    self.request, messages.ERROR, e.user_message,
                    extra_tags='error'
                )
                return HttpResponseRedirect(
                    reverse('matrix:order_confirmation')
                )
        context = self.get_context_data()
        context['domains_form'] = domains_form
        return self.render_to_response(context)

class OrderSuccessView(DetailView):
    template_name = "matrixhosting/order_success.html"
    context_object_name = "order"
    model = Order

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    @cache_control(no_cache=True, must_revalidate=True, no_store=True)
    def get(self, request, *args, **kwargs):
        context = {
             'order': self.request.session.get('order'),
             'balance': get_balance_for_user(self.request.user)
        }
        if ('order' not in request.session):
            return HttpResponseRedirect(reverse('matrix:index'))      
        return render(request, self.template_name, context)

class InvoiceDownloadView(View):
    template = 'matrixhosting/invoice.html'
    filename = 'invoice.pdf'

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
        bill = Bill.objects.get(id=self.request.session.get('bill_id'))
        if bill:
            context['bill'] = bill
            context['vat_rate'] = str(round(bill.vat_rate * 100, 2))
            context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
        return context

    def get(self, request):
        cmd_options = settings.REPORT_FORMAT
        return PDFTemplateResponse(request=request,
                                        template=self.template,
                                        filename = self.filename,
                                        cmd_options= cmd_options,
                                        footer_template= 'matrixhosting/includes/invoice_footer.html',
                                        context= self.get_context_data())
    
    
class OrdersView(ListView):
    template_name = "matrixhosting/orders.html"
    model = Order

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_queryset(self):
        return Order.objects.filter(owner=self.request.user)

    def post(self, request, *args, **kwargs):
        order = Order.objects.get(id=request.POST.get('order_id', 0))
        order.cancel()
        return JsonResponse({'message': 'Successfully Cancelled'})

class InstancesView(ListView):
    template_name = "matrixhosting/instances.html"
    model = VMInstance

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_queryset(self):
        return VMInstance.objects.filter(owner=self.request.user)

class PaymentsView(ListView):
    template_name = "matrixhosting/payments.html"
    model = Payment

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(PaymentsView, self).get_context_data(**kwargs)
        context.update({
            'balance': get_balance_for_user(self.request.user),
            'type': self.request.GET.get('type')
        })
        return context

    def get_queryset(self):
        if self.request.GET.get('type'):
            return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type')).order_by('-timestamp')
        return Payment.objects.filter(owner=self.request.user).order_by('-timestamp')

class CardsView(ListView):
    template_name = "matrixhosting/cards.html"
    model = StripeCreditCard

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(CardsView, self).get_context_data(**kwargs)
        customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
        setup_intent = uncloud_stripe.create_setup_intent(customer_id)

        context = super().get_context_data(**kwargs)
        context.update({
            'balance': get_balance_for_user(self.request.user),
            'client_secret': setup_intent.client_secret,
            'username': self.request.user.username,
            'stripe_pk':uncloud_stripe.public_api_key,
            'min_amount': settings.MIN_PER_TRANSACTION
        })
        return context

    def get_queryset(self):
        uncloud_stripe.sync_cards_for_user(self.request.user)
        return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')


