import calendar
import time

from datetime import datetime, date, timedelta
from dateutil.relativedelta import relativedelta
from django.db import models
from django.db.models import Q
from cms.models import CMSPlugin
from filer.fields.image import FilerImageField
from django.core.urlresolvers import reverse
from django.utils.functional import cached_property
from .mixins import Ordereable

# from membership.models import StripeCustomer
# from utils.models import BillingAddress


class MembershipType(models.Model):

    STANDARD = 'standard'
    MEMBERSHIP_TYPES = (
        (STANDARD, 'Standard'),

    )
    name = models.CharField(choices=MEMBERSHIP_TYPES, max_length=20)
    price = models.FloatField()

    @cached_property
    def days_left(self):
        current_date = date.today()
        _, days_in_month = calendar.monthrange(current_date.year, current_date.month)
        pass_days = current_date.day
        days_left = days_in_month - pass_days + 1
        return days_left

    @cached_property
    def first_month_price(self):
        current_date = date.today()
        _, days_in_month = calendar.monthrange(current_date.year, current_date.month)
        pass_days = current_date.day
        days_left = days_in_month - pass_days + 1
        percentage = days_left / days_in_month
        membership_price = self.price
        final_price = membership_price * percentage
        return final_price

    @cached_property
    def first_month_range(self):
        current_date = date.today()
        _, days_in_month = calendar.monthrange(current_date.year, current_date.month)
        pass_days = current_date.day
        days_left = days_in_month - pass_days
        end_date = current_date + timedelta(days=days_left)
        return current_date, end_date

    @cached_property
    def first_month_formated_range(self):
        start_date, end_date = self.first_month_range
        return "{} - {}".format(datetime.strftime(start_date, "%b, %d %Y"),
                                datetime.strftime(end_date, "%b, %d %Y"))

    @cached_property
    def next_month_in_sec_since_epoch(self):
        """
        First day of the next month expressed in seconds since the epoch time
        :return: Time in seconds
        """
        start_date, end_date = self.first_month_range
        first_day_next_month = end_date + timedelta(days=1)
        epoch_time = int(time.mktime(first_day_next_month.timetuple()))
        return epoch_time


class Membership(models.Model):
    type = models.ForeignKey(MembershipType)
    active = models.BooleanField(default=True)
    start_date = models.DateField()
    end_date = models.DateField()

    def __str__(self):
        return str(self.id)

    @classmethod
    def get_current_membership(cls, user):
        has_order_current_month = Q(
            membershiporder__customer__user=user,
            membershiporder__created_at__month=datetime.today().month
        )
        # import pdb;pdb.set_trace()
        return cls.objects.filter(has_order_current_month).last()

    # def get_current_active_membership(cls, user):
    #     membership = cls.get_current_membership(user)
    #     return membership if membership and membership.active else None

    @classmethod
    def get_by_user(cls, user):
        return cls.objects.filter(membershiporder__customer__user=user).last()

    @classmethod
    def create(cls, data):
        instance = cls.objects.create(**data)
        return instance

    @classmethod
    def activate_or_crete(cls, data, user):
        membership = cls.get_by_user(user)
        membership_id = membership.id if membership else None
        obj, created = cls.objects.update_or_create(
            id=membership_id, defaults=data
        )
        return obj

    @classmethod
    def is_digitalglarus_active_member(cls, user):
        # past_month = (datetime.today() - relativedelta(months=1)).month
        has_order_current_month = Q(
            membershiporder__customer__user=user,
            membershiporder__created_at__month=datetime.today().month
        )
        # has_order_past_month = Q(membershiporder__customer__user=user,
        #  membershiporder__created_at__month=past_month)
        active_membership = Q(active=True)
        # return cls.objects.filter(
        # has_order_past_month | has_order_current_month).\
        return cls.objects.filter(has_order_current_month).\
            filter(active_membership).exists()

    def update_dates(self, start_date, end_date):
        self.start_date = start_date
        self.end_date = end_date
        self.save()

    def deactivate(self):
        self.active = False
        self.save()

    def activate(self):
        self.active = True
        self.save()


class MembershipOrder(Ordereable, models.Model):
    membership = models.ForeignKey(Membership)
    start_date = models.DateField()
    end_date = models.DateField()
    stripe_subscription_id = models.CharField(max_length=100, null=True)

    @classmethod
    def current_membership_dates(cls, user):
        last_membership_payment = cls.objects.\
            filter(customer__user=user).last()
        if not last_membership_payment:
            return [None, None]

        return last_membership_payment.start_date, last_membership_payment.end_date

    @classmethod
    def next_membership_dates(cls, user):
        current_start_date, current_end_date = cls.current_membership_dates(user)
        if not current_start_date or not current_end_date:
            return [None, None]
        next_start_date = current_end_date + relativedelta(months=1)
        _, days_in_month = calendar.monthrange(next_start_date.year,
                                               next_start_date.month)
        next_start_date = next_start_date.replace(day=1)
        next_end_date = next_start_date + timedelta(days=days_in_month)
        return next_start_date, next_end_date

    def first_membership_range_date(self):
        start_date = self.created_at
        _, days_in_month = calendar.monthrange(start_date.year,
                                               start_date.month)
        pass_days = start_date.day
        days_left = days_in_month - pass_days
        end_date = start_date + timedelta(days=days_left)
        return start_date, end_date

    def get_membership_order_cc_data(self):
        return {
            'last4': self.last4,
            'cc_brand': self.cc_brand,
        }

    def get_membership_range_date(self):
        return self.start_date, self.end_date

    @classmethod
    def create(cls, data):
        stripe_charge = data.pop('stripe_charge', None)
        stripe_subscription_id = data.pop('stripe_subscription_id', None)
        instance = cls.objects.create(**data)
        instance.stripe_charge_id = stripe_charge.id
        instance.last4 = stripe_charge.source.last4
        instance.cc_brand = stripe_charge.source.brand
        instance.stripe_subscription_id = stripe_subscription_id
        instance.save()
        return instance


class BookingPrice(models.Model):
    price_per_day = models.FloatField()
    special_month_price = models.FloatField()


class Booking(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()
    price = models.FloatField()
    free_days = models.IntegerField(default=0)
    final_price = models.FloatField()

    @classmethod
    def create(cls, data):
        instance = cls.objects.create(**data)
        return instance

    @classmethod
    def get_ramaining_free_days(cls, user, start_date, end_date):

        TWO_DAYS = 2
        ONE_DAY = 1
        start_month = start_date.month
        end_month = end_date.month
        months = abs(start_month - (end_month + 12) if end_month < start_month
                     else end_month - start_month)
        current_date = datetime.today()
        current_month_bookings = cls.objects.filter(bookingorder__customer__user=user,
                                                    start_date__month=current_date.month)
        free_days_this_month = TWO_DAYS - sum(map(lambda x: x.free_days, current_month_bookings))

        if start_date == end_date and free_days_this_month == TWO_DAYS:
            free_days_this_month = ONE_DAY

        total_free_days = months * TWO_DAYS + free_days_this_month
        return total_free_days

    @classmethod
    def booking_price(cls, user, start_date, end_date):

        MAX_MONTH_PRICE = BookingPrice.objects.last().special_month_price
        MAX_MONTH_DAYS_PROMOTION = 31
        MIN_MONTH_DAYS_PROMOTION = 19

        booking_prices = BookingPrice.objects.last()
        price_per_day = booking_prices.price_per_day
        booking_days = (end_date - start_date).days + 1
        months = booking_days // MAX_MONTH_DAYS_PROMOTION
        remanent_days = booking_days % MAX_MONTH_DAYS_PROMOTION
        months_prices = months * MAX_MONTH_PRICE
        remanent_days_price = remanent_days * price_per_day \
            if remanent_days <= MIN_MONTH_DAYS_PROMOTION else MAX_MONTH_PRICE
        normal_price = months_prices + remanent_days_price

        free_days = cls.get_ramaining_free_days(user, start_date, end_date)

        final_booking_price = normal_price - (free_days * price_per_day)

        return normal_price, final_booking_price, free_days


class BookingOrder(Ordereable, models.Model):

    APPROVED, CANCELLED = range(1, 3)

    STATUS_CHOICES = (
        (APPROVED, 'Approved'),
        (CANCELLED, 'Cancelled')
    )

    booking = models.OneToOneField(Booking)
    original_price = models.FloatField()
    special_month_price = models.FloatField()
    status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=1)

    @classmethod
    def user_has_not_bookings(cls, user):
        return cls.objects.filter(customer__user=user).exists()

    def get_booking_cc_data(self):
        return {
            'last4': self.last4,
            'cc_brand': self.cc_brand,
        } if self.last4 and self.cc_brand else None

    def booking_days(self):
        return (self.booking.end_date - self.booking.start_date).days + 1

    def refund_required(self):
        days_to_start = (self.booking.start_date - datetime.today().date()).days
        return True if days_to_start < 7 else False

    def cancel(self):
        self.status = self.CANCELLED
        self.save()


class BookingCancellation(models.Model):

    order = models.ForeignKey(BookingOrder)
    created_at = models.DateTimeField(auto_now=True)
    required_refund = models.BooleanField(default=True)
    refund = models.BooleanField(default=False)

    def __str__(self):
        return "Order: {} - Required Refund: {}".format(self.order.id, self.refund)

    @classmethod
    def create(cls, booking_order):
        required_refund = booking_order.refund_required()
        cls.objects.create(order=booking_order, required_refund=required_refund)


class Supporter(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)

    def __str__(self):
        return "%s" % (self.name)

    def get_absolute_url(self):
        return reverse('dgSupporters_view', args=[self.pk])


class DGGallery(models.Model):
    parent = models.ForeignKey('self', blank=True, null=True)
    name = models.CharField(max_length=30)

    def __str__(self):
        return "%s" % (self.name)

    def get_absolute_url(self):
        return reverse('dgGallery_view', args=[self.pk])

    class Meta:
        verbose_name_plural = 'dgGallery'


class DGPicture(models.Model):
    gallery = models.ForeignKey(DGGallery)
    image = FilerImageField(related_name='dg_gallery')
    description = models.CharField(max_length=60)

    def __str__(self):
        return "%s" % (self.image.name)


class DGGalleryPlugin(CMSPlugin):
    dgGallery = models.ForeignKey(DGGallery)


class DGSupportersPlugin(CMSPlugin):
    pass