from pathlib import Path

from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.hashers import check_password
from django.core.exceptions import ValidationError
from django.utils import timezone, translation
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from i18nfield.forms import I18nModelForm

from pretalx.common.forms.fields import (
    IMAGE_EXTENSIONS, PasswordConfirmationField, PasswordField,
)
from pretalx.common.forms.widgets import MarkdownWidget
from pretalx.common.mixins.forms import PublicContent, ReadOnlyFlag
from pretalx.common.phrases import phrases
from pretalx.person.models import SpeakerInformation, SpeakerProfile, User
from pretalx.schedule.forms import AvailabilitiesFormMixin


class UserForm(forms.Form):
    login_email = forms.EmailField(
        max_length=60, label=phrases.base.enter_email, required=False
    )
    login_password = forms.CharField(
        widget=forms.PasswordInput, label=_('Password'), required=False
    )
    register_name = forms.CharField(label=_('Name'), required=False)
    register_email = forms.EmailField(label=_('Email address'), required=False)
    register_password = PasswordField(label=_('Password'), required=False)
    register_password_repeat = PasswordConfirmationField(
        label=_('Password (again)'), required=False, confirm_with='register_password'
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['register_email'].widget.attrs = {'placeholder': _('Email address')}

    def _clean_login(self, data):
        try:
            uname = User.objects.get(email__iexact=data.get('login_email')).email
        except User.DoesNotExist:  # We do this to avoid timing attacks
            uname = 'user@invalid'

        user = authenticate(username=uname, password=data.get('login_password'))

        if user is None:
            raise ValidationError(
                _(
                    'No user account matches the entered credentials. '
                    'Are you sure that you typed your password correctly?'
                )
            )

        if not user.is_active:
            raise ValidationError(_('Sorry, your account is currently disabled.'))

        data['user_id'] = user.pk

    def _clean_register(self, data):
        if data.get('register_password') != data.get('register_password_repeat'):
            self.add_error('register_password_repeat', ValidationError(phrases.base.passwords_differ))

        if User.objects.filter(email__iexact=data.get('register_email')).exists():
            self.add_error('register_email', ValidationError(
                _(
                    'We already have a user with that email address. Did you already register '
                    'before and just need to log in?'
                )
            ))

    def clean(self):
        data = super().clean()

        if data.get('login_email') and data.get('login_password'):
            self._clean_login(data)
        elif data.get('register_email') and data.get('register_password') and data.get('register_name'):
            self._clean_register(data)
        else:
            raise ValidationError(
                _(
                    'You need to fill all fields of either the login or the registration form.'
                )
            )

        return data

    def save(self):
        data = self.cleaned_data
        if data.get('login_email') and data.get('login_password'):
            return data['user_id']

        user = User.objects.create_user(
            name=data.get('register_name').strip(),
            email=data.get('register_email').lower().strip(),
            password=data.get('register_password'),
            locale=translation.get_language(),
            timezone=timezone.get_current_timezone_name(),
        )
        data['user_id'] = user.pk
        return user.pk


class SpeakerProfileForm(
    AvailabilitiesFormMixin, ReadOnlyFlag, PublicContent, forms.ModelForm
):
    USER_FIELDS = ['name', 'email', 'avatar', 'get_gravatar']
    FIRST_TIME_EXCLUDE = ['email']

    def __init__(self, *args, name=None, **kwargs):
        self.user = kwargs.pop('user', None)
        self.event = kwargs.pop('event', None)
        self.with_email = kwargs.pop('with_email', True)
        self.essential_only = kwargs.pop('essential_only', False)
        if self.user:
            kwargs['instance'] = self.user.event_profile(self.event)
        else:
            kwargs['instance'] = SpeakerProfile()
        super().__init__(*args, **kwargs, event=self.event)
        read_only = kwargs.get('read_only', False)
        initial = kwargs.get('initial', dict())
        initial['name'] = name
        for field in ('availabilities', 'biography'):
            if self.event and not getattr(
                self.event.settings, f'cfp_request_{field}', True
            ):
                self.fields.pop(field)
            else:
                self.fields[field].required = getattr(
                    self.event.settings, f'cfp_require_{field}', False
                )
        if self.user:
            initial.update({field: getattr(self.user, field) for field in self.user_fields})
        for field in self.user_fields:
            self.fields[field] = User._meta.get_field(field).formfield(
                initial=initial.get(field), disabled=read_only
            )

    @cached_property
    def user_fields(self):
        if self.user and not self.essential_only:
            return [f for f in self.USER_FIELDS if f != "email" or self.with_email]
        return [
            f
            for f in self.USER_FIELDS
            if f not in self.FIRST_TIME_EXCLUDE and (f != "email" or self.with_email)
        ]

    def clean_avatar(self):
        avatar = self.cleaned_data.get('avatar')
        if avatar:
            if (
                avatar.file
                and hasattr(avatar, '_size')
                and avatar._size > 10 * 1024 * 1024
            ):
                raise ValidationError(_('Your avatar may not be larger than 10 MB.'))
            extension = Path(avatar.name).suffix.lower()
            if extension not in IMAGE_EXTENSIONS:
                raise ValidationError(
                    _(
                        "This filetype is not allowed, it has to be one of the following: "
                    )
                    + ', '.join(IMAGE_EXTENSIONS)
                )
        return avatar

    def clean_email(self):
        email = self.cleaned_data.get('email')
        qs = User.objects.all()
        if self.user:
            qs = qs.exclude(pk=self.user.pk)
        if qs.filter(email__iexact=email):
            raise ValidationError(
                _('Please choose a different email address, this one is taken.')
            )
        return email

    def save(self, **kwargs):
        for user_attribute in self.user_fields:
            value = self.cleaned_data.get(user_attribute)
            if value is False and user_attribute == 'avatar':
                self.user.avatar = None
            else:
                setattr(self.user, user_attribute, value)
                self.user.save(update_fields=[user_attribute])

        self.instance.event = self.event
        self.instance.user = self.user
        super().save(**kwargs)

    class Meta:
        model = SpeakerProfile
        fields = ('biography',)
        public_fields = ['name', 'biography', 'avatar']
        widgets = {
            'biography': MarkdownWidget,
        }


class OrgaProfileForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('name', 'locale')


class OrgaSpeakerForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('name', 'email')


class LoginInfoForm(forms.ModelForm):
    error_messages = {
        'pw_current_wrong': _("The current password you entered was not correct.")
    }

    old_password = forms.CharField(
        widget=forms.PasswordInput, label=_('Password (current)'), required=True
    )
    password = PasswordField(label=_('New password'), required=False)
    password_repeat = PasswordConfirmationField(
        label=phrases.base.password_repeat, required=False, confirm_with='password'
    )

    def clean_old_password(self):
        old_pw = self.cleaned_data.get('old_password')
        if not check_password(old_pw, self.user.password):
            raise forms.ValidationError(
                self.error_messages['pw_current_wrong'], code='pw_current_wrong'
            )
        return old_pw

    def clean_email(self):
        email = self.cleaned_data.get('email')
        if User.objects.exclude(pk=self.user.pk).filter(email__iexact=email):
            raise ValidationError(_('Please choose a different email address.'))
        return email

    def clean(self):
        super().clean()
        password = self.cleaned_data.get('password')
        if password and not password == self.cleaned_data.get('password_repeat'):
            self.add_error('password_repeat', ValidationError(phrases.base.passwords_differ))

    def __init__(self, user, *args, **kwargs):
        self.user = user
        kwargs['instance'] = user
        super().__init__(*args, **kwargs)

    def save(self):
        super().save()
        password = self.cleaned_data.get('password')
        if password:
            self.user.set_password(password)
            self.user.save()

    class Meta:
        model = User
        fields = ('email',)


class SpeakerInformationForm(I18nModelForm):
    def clean(self):
        result = super().clean()
        if (
            self.cleaned_data['include_submitters']
            and self.cleaned_data['exclude_unconfirmed']
        ):
            self.add_error('exclude_unconfirmed', ValidationError(
                _(
                    'Either target all submitters or only confirmed speakers, these options are exclusive!'
                )
            ))
        return result

    class Meta:
        model = SpeakerInformation
        fields = (
            'title',
            'text',
            'include_submitters',
            'exclude_unconfirmed',
            'resource',
        )


class SpeakerFilterForm(forms.Form):
    role = forms.ChoiceField(
        choices=(
            ('', _('all')),
            ('true', _('Speakers')),
            ('false', _('Non-accepted submitters')),
        ),
        required=False,
    )
