from __future__ import unicode_literals
from calendar import monthrange
from datetime import date
import re

from django import forms
from django.core import validators
from django.utils.translation import ugettext_lazy as _

from . import get_credit_card_issuer
from .widgets import CreditCardExpiryWidget, CreditCardNumberWidget


class CreditCardNumberField(forms.CharField):

    widget = CreditCardNumberWidget(attrs={'autocomplete': 'cc-number'})
    default_error_messages = {
        'invalid': _('Please enter a valid card number'),
        'invalid_type': _('We accept only %(valid_types)s')}

    def __init__(self, valid_types=None, *args, **kwargs):
        self.valid_types = valid_types
        kwargs['max_length'] = kwargs.pop('max_length', 32)
        super(CreditCardNumberField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if value is not None:
            value = re.sub('[\s-]+', '', value)
        return super(CreditCardNumberField, self).to_python(value)

    def validate(self, value):
        card_type, issuer_name = get_credit_card_issuer(value)
        if value in validators.EMPTY_VALUES and self.required:
            raise forms.ValidationError(self.error_messages['required'])
        if value and not self.cart_number_checksum_validation(self, value):
            raise forms.ValidationError(self.error_messages['invalid'])
        if (value and not self.valid_types is None
                and not card_type in self.valid_types):
            valid_types = map(issuer_name, self.valid_types)
            error_message = self.error_messages['invalid_type'] % {
                'valid_types': ', '.join(valid_types)
            }
            raise forms.ValidationError(error_message)

    @staticmethod
    def cart_number_checksum_validation(cls, number):
        digits = []
        even = False
        if not number.isdigit():
            return False
        for digit in reversed(number):
            digit = ord(digit) - ord('0')
            if even:
                digit *= 2
                if digit >= 10:
                    digit = digit % 10 + digit // 10
            digits.append(digit)
            even = not even
        return sum(digits) % 10 == 0 if digits else False


class CreditCardExpiryField(forms.MultiValueField):

    EXP_MONTH = [(str(x), '%02d' % (x,)) for x in range(1, 13)]
    EXP_YEAR = [(str(x), str(x)) for x in range(date.today().year,
                                                date.today().year + 15)]

    default_error_messages = {
        'invalid_month': 'Enter a valid month.',
        'invalid_year': 'Enter a valid year.'}

    def __init__(self, *args, **kwargs):
        errors = self.default_error_messages.copy()
        if 'error_messages' in kwargs:
            errors.update(kwargs['error_messages'])

        fields = (
            forms.ChoiceField(
                choices=[('', _('Month'))] + self.EXP_MONTH,
                error_messages={'invalid': errors['invalid_month']},
                widget=forms.Select(
                    attrs={'autocomplete': 'cc-exp-month'})),
            forms.ChoiceField(
                choices=[('', _('Year'))] + self.EXP_YEAR,
                error_messages={'invalid': errors['invalid_year']},
                widget=forms.Select(
                    attrs={'autocomplete': 'cc-exp-year'})),
        )

        super(CreditCardExpiryField, self).__init__(fields, *args, **kwargs)
        self.widget = CreditCardExpiryWidget(widgets=[fields[0].widget,
                                                      fields[1].widget])

    def clean(self, value):
        exp = super(CreditCardExpiryField, self).clean(value)
        if date.today() > exp:
            raise forms.ValidationError(
                "The expiration date you entered is in the past.")
        return exp

    def compress(self, data_list):
        if data_list:
            if data_list[1] in forms.fields.EMPTY_VALUES:
                error = self.error_messages['invalid_year']
                raise forms.ValidationError(error)
            if data_list[0] in forms.fields.EMPTY_VALUES:
                error = self.error_messages['invalid_month']
                raise forms.ValidationError(error)
            year = int(data_list[1])
            month = int(data_list[0])
            # find last day of the month
            day = monthrange(year, month)[1]
            return date(year, month, day)
        return None


class CreditCardVerificationField(forms.CharField):

    widget = forms.TextInput(attrs={'autocomplete': 'cc-csc'})
    default_error_messages = {
        'invalid': _('Enter a valid security number.')}

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = kwargs.pop('max_length', 4)
        super(CreditCardVerificationField, self).__init__(*args, **kwargs)

    def validate(self, value):
        if value in validators.EMPTY_VALUES and self.required:
            raise forms.ValidationError(self.error_messages['required'])
        if value and not re.match('^[0-9]{3,4}$', value):
            raise forms.ValidationError(self.error_messages['invalid'])


class CreditCardNameField(forms.CharField):

    widget = forms.TextInput(attrs={'autocomplete': 'cc-name'})
