import requests
import six
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms import ALL_FIELDS, BaseModelForm, forms
from django.forms.models import ModelFormMetaclass
from django.utils.translation import ugettext_lazy as _

from .renderer import SPAFormFieldWidgetRenderer

DEFAULT_VALIDATION_ERROR = _('Invalid data')
NO_COOKIE_MESSAGE = _('Please activate cookies to submit the form.')
SUBMIT_BUTTON_LABEL = _('Submit')


class SpaApiForm(forms.Form):
    api_url = ''
    default_validation_error = DEFAULT_VALIDATION_ERROR
    submit_button_label = SUBMIT_BUTTON_LABEL
    show_general_error_message = True
    uses_recaptcha = True

    def get_api_url(self):
        return str(self.api_url)

    def get_spa_data_dict(self):
        """
        Returns a dictionary that is used to render the form.
        """
        data_dict = self.get_form_state_and_messages_dict()
        data_dict['url'] = self.get_api_url()
        data_dict['fields'] = self.get_fields_data_dict()
        return data_dict

    def get_fields_data_dict(self):
        fields = [self.get_form_field_data_dict(name=name, field=field) for name, field in self.fields.items()]
        fields.append(self.get_submit_button())
        return fields

    def get_form_state_and_messages_dict(self):
        data_dict = {
            'state': {
                'error': bool(self.errors)
            }
        }

        error_messages = []
        if self.errors and self.show_general_error_message:
            error_messages.append(str(self.default_validation_error))
        non_field_errors = self.errors.get(ALL_FIELDS)
        if non_field_errors:
            error_messages.extend(non_field_errors)
        if error_messages:
            data_dict['messages'] = {
                'error': error_messages
            }

        return data_dict

    def get_form_field_data_dict(self, name, field):
        renderer = SPAFormFieldWidgetRenderer(form=self, field=field, name=name)
        field_data = renderer.render()
        return field_data

    def get_submit_button_label(self):
        return self.submit_button_label

    def get_submit_button(self):
        data = {
            'component': 'cmp-form-submit',
            'label': str(self.get_submit_button_label()),
            'useGrecaptcha': False,
        }
        return data

    def get_api_response_data_dict(self):
        """
        Returns a dictionary with state and messages for the form and all its fields.
        """
        data_dict = self.get_form_state_and_messages_dict()
        fields = []

        for field_name, field in self.fields.items():
            field_has_errors = field_name in self.errors.keys()
            field_data = {
                'id': field_name,
                'state': {
                    'error': bool(field_has_errors),
                }
            }

            if field_has_errors:
                field_data['messages'] = {
                    'error': self.errors[field_name]
                }

            fields.append(field_data)

        data_dict['fields'] = fields

        return data_dict


class ReCaptchaFormMixin(object):
    invalid_recaptcha = _('Invalid reCAPTCHA')

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request', None)
        super(ReCaptchaFormMixin, self).__init__(*args, **kwargs)

    def clean(self):
        if settings.RECAPTCHA_IS_ACTIVE:
            self.verify()
        return super(ReCaptchaFormMixin, self).clean()

    def verify(self):
        try:
            remote_ip = self.request._request.META.get('REMOTE_ADDR')
        except AttributeError:
            raise ValueError('ReCaptchaFormMixin has no request specified.')

        try:
            recaptcha_form_value = self.request.data['g-recaptcha-response']
        except:
            raise ValidationError(self.invalid_recaptcha)

        response = requests.post(
            url=settings.RECAPTCHA_URL,
            data={
                'secret': settings.RECAPTCHA_SECRET_KEY,
                'response': recaptcha_form_value,
                'remoteip': remote_ip
            }
        )

        if response.status_code == 200:
            response_json_data = response.json()
            if response_json_data.get('success'):
                return True

        raise ValidationError(self.invalid_recaptcha)

    def get_submit_button(self):
        data = super(ReCaptchaFormMixin, self).get_submit_button()
        data['useGrecaptcha'] = True
        data['recaptcha_sitekey'] = settings.RECAPTCHA_SITE_KEY
        return data


class SpaApiModelForm(six.with_metaclass(ModelFormMetaclass, BaseModelForm)):
    default_validation_error = DEFAULT_VALIDATION_ERROR
    no_cookie_message = NO_COOKIE_MESSAGE
    submit_button_label = SUBMIT_BUTTON_LABEL
    show_general_error_message = True

    def get_api_url(self):
        return ''

    def get_spa_data_dict(self):
        """
        Returns a dictionary that is used to render the form.
        """
        data_dict = self._get_form_state_and_messages_dict()
        data_dict['url'] = self.get_api_url()
        data_dict['no_cookie_message'] = str(self.no_cookie_message)
        data_dict['fields'] = self.get_fields_data_dict()
        return data_dict

    def get_api_response_data_dict(self):
        """
        Returns a dictionary with state and messages for the form and all its fields.
        """
        data_dict = self._get_form_state_and_messages_dict()
        fields = []

        for field_name, field in self.fields.items():
            field_has_errors = field_name in self.errors.keys()
            field_data = {
                'id': field_name,
                'state': {
                    'error': field_has_errors,
                }
            }

            if field_has_errors:
                field_data['messages'] = {
                    'error': self.errors[field_name]
                }

            fields.append(field_data)

        data_dict['fields'] = fields

        return data_dict

    def get_fields_data_dict(self):
        fields = [self._get_form_field_data_dict(name=name, field=field) for name, field in self.fields.items()]
        fields.append(self.get_submit_button())
        return fields

    def get_submit_button(self):
        return {
            'component': 'cmp-form-submit',
            'label': str(self.submit_button_label),
        }

    def _get_form_state_and_messages_dict(self):
        data_dict = {
            'state': {
                'error': bool(self.errors)
            }
        }

        error_messages = []
        if self.errors and self.show_general_error_message:
            error_messages.append(str(self.default_validation_error))
        non_field_errors = self.errors.get(ALL_FIELDS)
        if non_field_errors:
            error_messages += non_field_errors
        if error_messages:
            data_dict['messages'] = {
                'error': error_messages
            }

        return data_dict

    def _get_form_field_data_dict(self, name, field):
        renderer = SPAFormFieldWidgetRenderer(form=self, field=field, name=name)
        field_data = renderer.render()
        return field_data


class SpaApiFieldsetModelForm(SpaApiModelForm):
    def get_spa_data_dict(self):
        data_dict = self._get_form_state_and_messages_dict()
        data_dict['url'] = self.get_api_url()
        data_dict['no_cookie_message'] = str(self.no_cookie_message)
        data_dict['fieldsets'] = self.get_fieldset_data_dict()
        return data_dict

    def get_fieldset_data_dict(self):
        fieldsets = []

        for fieldset in self.fieldsets:
            fieldset_name = str(fieldset[0])
            fieldset_fields = fieldset[1].get('fields')
            fieldset_data = {
                'name': fieldset_name,
                'fields': []
            }

            for field_name in fieldset_fields:
                field = self.fields.get(field_name)
                fieldset_data['fields'].append(self._get_form_field_data_dict(name=field_name, field=field))

            fieldsets.append(fieldset_data)

        fieldsets[-1]['fields'].append(self.get_submit_button())

        return fieldsets
