# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from django import forms
from django.forms.models import modelform_factory as dj_modelform_factory
from django.utils.translation import ugettext
from django.utils.translation import ugettext_lazy as _

from authentic2 import app_settings, models
from authentic2.custom_user.models import User

from .fields import ValidatedEmailField
from .mixins import LockedFieldFormMixin
from .utils import NextUrlFormMixin


class DeleteAccountForm(forms.Form):
    password = forms.CharField(widget=forms.PasswordInput, label=_("Password"))

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user')
        super().__init__(*args, **kwargs)

    def clean_password(self):
        password = self.cleaned_data.get('password')
        if password and not self.user.check_password(password):
            raise forms.ValidationError(ugettext('Password is invalid'))
        return password


class EmailChangeFormNoPassword(forms.Form):
    email = ValidatedEmailField(label=_('New email'))

    def __init__(self, user, *args, **kwargs):
        self.user = user
        super().__init__(*args, **kwargs)


class EmailChangeForm(EmailChangeFormNoPassword):
    password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)

    def clean_email(self):
        email = self.cleaned_data['email']
        if email == self.user.email:
            raise forms.ValidationError(_('This is already your email address.'))
        return email

    def clean_password(self):
        password = self.cleaned_data["password"]
        if not self.user.check_password(password):
            raise forms.ValidationError(
                _('Incorrect password.'),
                code='password_incorrect',
            )
        return password


class BaseUserForm(LockedFieldFormMixin, forms.ModelForm):
    error_messages = {
        'duplicate_username': _("A user with that username already exists."),
    }

    def __init__(self, *args, **kwargs):
        from authentic2 import models

        self.attributes = models.Attribute.objects.all()
        initial = kwargs.setdefault('initial', {})
        instance = kwargs.get('instance')
        # extended attributes are not model fields, their initial value must be
        # explicitely defined
        self.atvs = []
        self.locked_fields = set()
        if instance:
            self.atvs = models.AttributeValue.objects.select_related('attribute').with_owner(instance)
            for atv in self.atvs:
                name = atv.attribute.name
                if name in self.declared_fields:
                    initial[name] = atv.to_python()
                # helper data for LockedFieldFormMixin
                if atv.verified:
                    self.locked_fields.add(name)
        super().__init__(*args, **kwargs)

    def is_field_locked(self, name):
        # helper method for LockedFieldFormMixin
        return name in self.locked_fields

    def save_attributes(self):
        # only save non verified attributes here
        verified_attributes = set(
            self.instance.attribute_values.filter(verified=True).values_list('attribute__name', flat=True)
        )
        for attribute in self.attributes:
            name = attribute.name
            if name in self.fields and name not in verified_attributes:
                value = self.cleaned_data[name]
                setattr(self.instance.attributes, name, value)

    def save(self, commit=True):
        result = super().save(commit=commit)
        if commit:
            self.save_attributes()
        else:
            old = self.save_m2m

            def save_m2m(*args, **kwargs):
                old(*args, **kwargs)
                self.save_attributes()

            self.save_m2m = save_m2m
        return result


class EditProfileForm(NextUrlFormMixin, BaseUserForm):
    pass


def modelform_factory(model, **kwargs):
    """Build a modelform for the given model,

    For the user model also add attribute based fields.
    """

    form = kwargs.pop('form', None)
    fields = kwargs.get('fields') or []
    required = list(kwargs.pop('required', []) or [])
    d = {}
    # KV attributes are only supported for the user model currently
    modelform = None
    if issubclass(model, User):
        if not form:
            form = BaseUserForm
        attributes = models.Attribute.objects.all()
        for attribute in attributes:
            if attribute.name not in fields:
                continue
            d[attribute.name] = attribute.get_form_field()
        for field in app_settings.A2_REQUIRED_FIELDS:
            if field not in required:
                required.append(field)
    if not form or not hasattr(form, 'Meta'):
        meta_d = {'model': model, 'fields': '__all__'}
        meta = type('Meta', (), meta_d)
        d['Meta'] = meta
    if not form:  # fallback
        form = forms.ModelForm
    modelform = None
    if required:

        def __init__(self, *args, **kwargs):
            super(modelform, self).__init__(*args, **kwargs)
            for field in required:
                if field in self.fields:
                    self.fields[field].required = True

        d['__init__'] = __init__
    modelform = type(model.__name__ + 'ModelForm', (form,), d)
    kwargs['form'] = modelform
    modelform.required_css_class = 'form-field-required'
    return dj_modelform_factory(model, **kwargs)
