# -*- coding: utf-8 -*-
import datetime
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from django.http import HttpResponse
from django.db import transaction
from django.db.models import Q
from coin.offers.models import Offer, OfferSubscription
from coin.members.models import Member
from coin.billing.models import Invoice, InvoiceDetail


def create_all_members_invoices_for_a_period(date=datetime.date.today()):
    """
    Pour chaque membre ayant au moins un abonnement actif, génère les factures
    en prenant la date comme premier mois de la période de facturation
    """
    members = Member.objects.filter(
        Q(offersubscription__resign_date__isnull=True) |
        Q(offersubscription__resign_date__gte=date))

    for member in members:
        create_member_invoice_for_a_period(member,date)

@transaction.atomic
def create_member_invoice_for_a_period(member, date):
    """
    Créé si necessaire une facture pour un membre en prenant la date passée
    en paramètre comme premier mois de période. Renvoi la facture générée
    ou None si aucune facture n'était necessaire.
    """
    sid = transaction.savepoint()

    date_first_of_month = datetime.date(date.year, date.month, 1)
    date_last_of_month = (date_first_of_month + relativedelta(months=+1) -
                          relativedelta(days=+1))

    invoice = Invoice.objects.create(
        date_due=datetime.date.today(),
        member=member
    )

    # Récupère les abonnements actifs du membre à la fin du mois
    offer_subscriptions = member.get_active_subscriptions(date_last_of_month)

    # Pour chaque abonnement
    for offer_subscription in offer_subscriptions:
        # Récupère l'offre de l'abonnement
        offer = offer_subscription.offer

        # Vérifie s'il s'agit de la première facture d'un abonnement,
        # Alors facture en plus les frais de mise en service
        invoicedetail_test_first = InvoiceDetail.objects.filter(
            offersubscription__exact=offer_subscription.pk,
            invoice__member__exact=member.pk)
        if not invoicedetail_test_first.exists():
           invoice.details.create(
                label=offer.name + " - Frais de mise en service",
                amount=offer.initial_fees,
                offersubscription=offer_subscription,
                period_from=None,
                period_to=None)

        # Période de facturation de l'item par defaut
        # - Du début du mois de la date passée en paramètre
        # - Jusqu'à la fin du mois de la période de facturation de l'offre
        period_from = date_first_of_month
        period_to = (date_first_of_month +
                     relativedelta(months=+offer.billing_period) -
                     relativedelta(days=+1))
        planned_period_number_of_days = (period_to - period_from).days + 1
        quantity = 1

        # Si la facture est le premier mois de l'abonnement, alors met la 
        # date de début de facturation au jour de l'ouverture de
        # l'abonnement
        if date_first_of_month == datetime.date(
            offer_subscription.subscription_date.year, 
            offer_subscription.subscription_date.month, 1):
            period_from = offer_subscription.subscription_date

        # Recherche dans les factures déjà existantes de ce membre des
        # items ayant cet abonnement pour lesquels la période de
        # facturation englobe le début de notre période de facturation
        # actuelle
        invoicedetail_test_before = InvoiceDetail.objects.filter(
            offersubscription__exact=offer_subscription.pk,
            period_from__lte=period_from,
            period_to__gt=period_from,
            invoice__member__exact=member.pk)

        # Si une facture de ce genre existe alors ne fait rien.
        if not invoicedetail_test_before.exists():

            # Recherche dans les factures déjà existantes de ce membre des
            # items ayant cet abonnement pour lesquels la période de
            # facturation commence avant la fin de notre période de facturation
            # actuelle
            invoicedetail_test_after = InvoiceDetail.objects.filter(
                offersubscription__exact=offer_subscription.pk,
                period_from__lte=period_to,
                period_from__gte=period_from,
                invoice__member__exact=member.pk)

            # Si une telle facture existe, récupère la date de début de
            # facturation pour en faire la date de fin de facturation
            if invoicedetail_test_after.exists():
                invoicedetail_after = invoicedetail_test_after.first()
                period_to = (
                    datetime.date(invoicedetail_after.period_from.year,
                                  invoicedetail_after.period_from.month, 1) -
                    relativedelta(days=+1))

            # Si la période de facturation varie par rapport à celle prévue par
            # l'offre, calcul au prorata en faisant varier la quantité
            period_number_of_days = (period_to - period_from).days + 1
            if (planned_period_number_of_days != period_number_of_days):
                quantity = (Decimal(period_number_of_days) /
                            Decimal(planned_period_number_of_days))

            # Si durée de 0jours ou dates incohérentes, alors on ajoute pas
            # (Si la period est de 0jours c'est que la facture existe déjà.)
            if (period_from<period_to):
                # Ajout l'item de l'offre correspondant à l'abonnement
                # à la facture
                invoice.details.create(label=offer.name,
                                       amount=offer.period_fees,
                                       quantity=quantity,
                                       offersubscription=offer_subscription,
                                       period_from=period_from,
                                       period_to=period_to)
    
    # S'il n'y a pas d'items dans la facture, ne commit pas la transaction.
    if (invoice.details.count()>0):
        invoice.save()
        transaction.savepoint_commit(sid)
        return invoice
    else:
        transaction.savepoint_rollback(sid)
        return None
