import datetime
import logging

import pyotp
import requests
import stripe
from django.conf import settings
from django.contrib.sites.models import Site

from datacenterlight.tasks import create_vm_task
from hosting.models import HostingOrder, HostingBill, OrderDetail
from membership.models import StripeCustomer
from utils.forms import UserBillingAddressForm
from utils.models import BillingAddress, UserBillingAddress
from utils.stripe_utils import StripeUtils
from .cms_models import CMSIntegration
from .models import VMPricing, VMTemplate

logger = logging.getLogger(__name__)

eu_countries = ['at', 'be', 'bg', 'ch', 'cy', 'cz', 'hr', 'dk',
                'ee', 'fi', 'fr', 'mc', 'de', 'gr', 'hu', 'ie', 'it',
                'lv', 'lu', 'mt', 'nl', 'pl', 'pt', 'ro','sk', 'si', 'es',
                'se', 'gb']


def get_cms_integration(name):
    current_site = Site.objects.get_current()
    try:
        cms_integration = CMSIntegration.objects.get(
            name=name, domain=current_site
        )
    except CMSIntegration.DoesNotExist:
        cms_integration = CMSIntegration.objects.get(name=name, domain=None)
    return cms_integration


def create_vm(billing_address_data, stripe_customer_id, specs,
              stripe_subscription_obj, card_details_dict, request,
              vm_template_id, template, user):
    logger.debug("In create_vm")
    billing_address = BillingAddress(
        cardholder_name=billing_address_data['cardholder_name'],
        street_address=billing_address_data['street_address'],
        city=billing_address_data['city'],
        postal_code=billing_address_data['postal_code'],
        country=billing_address_data['country'],
        vat_number=billing_address_data['vat_number'],
    )
    billing_address.save()
    customer = StripeCustomer.objects.filter(id=stripe_customer_id).first()
    vm_pricing = (
        VMPricing.get_vm_pricing_by_name(name=specs['pricing_name'])
        if 'pricing_name' in specs else
        VMPricing.get_default_pricing()
    )

    final_price = (
        specs.get('total_price')
        if 'total_price' in specs
        else specs.get('price')
    )

    # Create a Hosting Order with vm_id = 0, we shall set it later in
    # celery task once the VM instance is up and running
    order = HostingOrder.create(
        price=final_price,
        customer=customer,
        billing_address=billing_address,
        vm_pricing=vm_pricing
    )

    order_detail_obj, obj_created = OrderDetail.objects.get_or_create(
        vm_template=VMTemplate.objects.get(
            opennebula_vm_template_id=vm_template_id
        ),
        cores=specs['cpu'], memory=specs['memory'], ssd_size=specs['disk_size']
    )
    order.order_detail = order_detail_obj
    order.save()

    # Create a Hosting Bill
    HostingBill.create(customer=customer, billing_address=billing_address)

    # Create Billing Address for User if he does not have one
    if not customer.user.billing_addresses.count():
        billing_address_data.update({
            'user': customer.user.id
        })
        billing_address_user_form = UserBillingAddressForm(
            billing_address_data
        )
        billing_address_user_form.is_valid()
        billing_address_user_form.save()

    # Associate the given stripe subscription with the order
    order.set_subscription_id(
        stripe_subscription_obj.id, card_details_dict
    )

    # Set order status approved
    order.set_approved()

    create_vm_task.delay(vm_template_id, user, specs, template, order.id)


def clear_all_session_vars(request):
    if request.session is not None:
        for session_var in ['specs', 'template', 'billing_address',
                            'billing_address_data', 'card_id',
                            'token', 'customer', 'generic_payment_type',
                            'generic_payment_details', 'product_id',
                            'order_confirm_url', 'new_user_hosting_key_id',
                            'vat_validation_status', 'billing_address_id',
                            'id_payment_method']:
            if session_var in request.session:
                del request.session[session_var]


def check_otp(name, realm, token):
    data = {
        "auth_name": settings.AUTH_NAME,
        "auth_token": pyotp.TOTP(settings.AUTH_SEED).now(),
        "auth_realm": settings.AUTH_REALM,
        "name": name,
        "realm": realm,
        "token": token
    }
    response = requests.post(
        "https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format(
            OTP_SERVER=settings.OTP_SERVER,
            OTP_VERIFY_ENDPOINT=settings.OTP_VERIFY_ENDPOINT
        ),
        data=data
    )
    return response.status_code


def validate_vat_number(stripe_customer_id, billing_address_id,
                        is_user_ba=False):
    if is_user_ba:
        try:
            billing_address = UserBillingAddress.objects.get(
                id=billing_address_id)
        except UserBillingAddress.DoesNotExist as dne:
            billing_address = None
            logger.debug(
                "UserBillingAddress does not exist for %s" % billing_address_id)
        except UserBillingAddress.MultipleObjectsReturned as mor:
            logger.debug(
                "Multiple UserBillingAddress exist for %s" % billing_address_id)
            billing_address = UserBillingAddress.objects.filter(
                id=billing_address_id).order_by('-id').first()
    else:
        try:
            billing_address = BillingAddress.objects.get(id=billing_address_id)
        except BillingAddress.DoesNotExist as dne:
            billing_address = None
            logger.debug("BillingAddress does not exist for %s" % billing_address_id)
        except BillingAddress.MultipleObjectsReturned as mor:
            logger.debug("Multiple BillingAddress exist for %s" % billing_address_id)
            billing_address = BillingAddress.objects.filter(id=billing_address_id).order_by('-id').first()
    if billing_address is not None:
        logger.debug("BillingAddress found: %s %s type=%s" % (
            billing_address_id, str(billing_address), type(billing_address)))
        if billing_address.country.lower().strip() not in eu_countries:
            return {
                "validated_on": "",
                "status": "not_needed"
            }
        if billing_address.vat_number_validated_on:
            logger.debug("billing_address verified on %s" %
                         billing_address.vat_number_validated_on)
            return {
                "validated_on": billing_address.vat_number_validated_on,
                "status": "verified"
            }
        else:
            logger.debug("billing_address not yet verified, "
                         "Checking if we already have a tax id")
            if billing_address.stripe_tax_id:
                logger.debug("We have a tax id %s" % billing_address.stripe_tax_id)
                tax_id_obj = stripe.Customer.retrieve_tax_id(
                  stripe_customer_id,
                  billing_address.stripe_tax_id,
                )
                if tax_id_obj.verification.status == "verified":
                    logger.debug("Latest status on Stripe=%s. Updating" %
                                 tax_id_obj.verification.status)
                    # update billing address
                    billing_address.vat_number_validated_on = datetime.datetime.now()
                    billing_address.vat_validation_status = tax_id_obj.verification.status
                    billing_address.save()
                    return {
                        "status": "verified",
                        "validated_on": billing_address.vat_number_validated_on
                    }
                else:
                    billing_address.vat_validation_status = tax_id_obj.verification.status
                    billing_address.save()
                    logger.debug(
                        "Latest status on Stripe=%s" % str(tax_id_obj)
                    )
                    return {
                        "status": tax_id_obj.verification.status if tax_id_obj
                        else "unknown",
                        "validated_on": ""
                    }
            else:
                logger.debug("Creating a tax id")
                logger.debug("Billing address = %s" % str(billing_address))
                tax_id_obj = create_tax_id(
                    stripe_customer_id, billing_address_id,
                    "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat",
                    is_user_ba=is_user_ba
                )
                logger.debug("tax_id_obj = %s" % str(tax_id_obj))
    else:
        logger.debug("invalid billing address")
        return {
            "status": "invalid billing address",
            "validated_on": ""
        }

    if 'response_object' in tax_id_obj:
        return tax_id_obj

    return {
        "status": tax_id_obj.verification.status,
        "validated_on": datetime.datetime.now() if tax_id_obj.verification.status == "verified" else ""
    }


def create_tax_id(stripe_customer_id, billing_address_id, type,
                  is_user_ba=False):
    if is_user_ba:
        try:
            billing_address = UserBillingAddress.objects.get(
                id=billing_address_id)
        except UserBillingAddress.DoesNotExist as dne:
            billing_address = None
            logger.debug(
                "UserBillingAddress does not exist for %s" % billing_address_id)
        except UserBillingAddress.MultipleObjectsReturned as mor:
            logger.debug(
                "Multiple UserBillingAddress exist for %s" % billing_address_id)
            billing_address = UserBillingAddress.objects.filter(
                id=billing_address_id).order_by('-id').first()
    else:
        try:
            billing_address = BillingAddress.objects.get(id=billing_address_id)
        except BillingAddress.DoesNotExist as dne:
            billing_address = None
            logger.debug("BillingAddress does not exist for %s" % billing_address_id)
        except BillingAddress.MultipleObjectsReturned as mor:
            logger.debug("Multiple BillingAddress exist for %s" % billing_address_id)
            billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first()

    tax_id_obj = None
    if billing_address:
        stripe_utils = StripeUtils()
        tax_id_response = stripe_utils.get_or_create_tax_id_for_user(
            stripe_customer_id,
            vat_number=billing_address.vat_number,
            type=type,
            country=billing_address.country
        )

        tax_id_obj = tax_id_response.get('response_object')

    if not tax_id_obj:
        logger.debug("Received none in tax_id_obj")
        return {
            'paid': False,
            'response_object': None,
            'error': "No such address found" if 'error' not in tax_id_response else
            tax_id_response["error"]
        }

    try:
        stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id)
        billing_address_set = set()
        logger.debug("Updating billing address")
        for ho in stripe_customer.hostingorder_set.all():
            if ho.billing_address.vat_number == billing_address.vat_number:
                billing_address_set.add(ho.billing_address)
        for b_address in billing_address_set:
            b_address.stripe_tax_id = tax_id_obj.id
            b_address.vat_validation_status = tax_id_obj.verification.status
            b_address.save()
            logger.debug("Updated billing_address %s" % str(b_address))

        ub_addresses = stripe_customer.user.billing_addresses.filter(
            vat_number=billing_address.vat_number)
        for ub_address in ub_addresses:
            ub_address.stripe_tax_id = tax_id_obj.id
            ub_address.vat_validation_status = tax_id_obj.verification.status
            ub_address.save()
            logger.debug("Updated user_billing_address %s" % str(ub_address))
    except StripeCustomer.DoesNotExist as dne:
        logger.debug("StripeCustomer %s does not exist" % stripe_customer_id)
        billing_address.stripe_tax_id = tax_id_obj.id
        billing_address.vat_validation_status = tax_id_obj.verification.status
        billing_address.save()
    return tax_id_obj