from django import forms
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.forms import AuthenticationForm as BaseAuthenticationForm
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.utils.translation import gettext_lazy as _

from ..bans import get_user_ban

User = get_user_model()


class MisagoAuthMixin:
    error_messages = {
        "empty_data": _("Fill out both fields."),
        "invalid_login": _("Login or password is incorrect."),
        "inactive_user": _(
            "You have to activate your account before you will be able to sign in."
        ),
        "inactive_admin": _(
            "Your account has to be activated by site administrator "
            "before you will be able to sign in."
        ),
    }

    def confirm_user_active(self, user):
        if user.requires_activation_by_admin:
            raise ValidationError(
                self.error_messages["inactive_admin"], code="inactive_admin"
            )

        if user.requires_activation_by_user:
            raise ValidationError(
                self.error_messages["inactive_user"], code="inactive_user"
            )

    def confirm_user_not_banned(self, user):
        if not user.is_staff:
            self.user_ban = get_user_ban(user, self.request.cache_versions)
            if self.user_ban:
                raise ValidationError("", code="banned")

    def get_errors_dict(self):
        error = self.errors.as_data()["__all__"][0]
        if error.code == "banned":
            error.message = self.user_ban.ban.get_serialized_message()
        else:
            error.message = error.messages[0]

        return {"detail": error.message, "code": error.code}


class AuthenticationForm(MisagoAuthMixin, BaseAuthenticationForm):
    """
    Base class for authenticating users, Floppy-forms and
    Misago login field compliant
    """

    username = forms.CharField(
        label=_("Username or e-mail"), required=False, max_length=254
    )
    password = forms.CharField(
        label=_("Password"), strip=False, required=False, widget=forms.PasswordInput
    )

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

    def clean(self):
        username = self.cleaned_data.get("username")
        password = self.cleaned_data.get("password")

        if username and password:
            self.user_cache = authenticate(username=username, password=password)

            if self.user_cache is None or not self.user_cache.is_active:
                raise ValidationError(
                    self.error_messages["invalid_login"], code="invalid_login"
                )
            else:
                self.confirm_login_allowed(self.user_cache)
        else:
            raise ValidationError(self.error_messages["empty_data"], code="empty_data")

        return self.cleaned_data

    def confirm_login_allowed(self, user):
        self.confirm_user_active(user)
        self.confirm_user_not_banned(user)


class AdminAuthenticationForm(AuthenticationForm):
    required_css_class = "required"

    def __init__(self, *args, **kwargs):
        self.error_messages.update(
            {"not_staff": _("Your account does not have admin privileges.")}
        )

        super().__init__(*args, **kwargs)

    def confirm_login_allowed(self, user):
        if not user.is_staff:
            raise forms.ValidationError(
                self.error_messages["not_staff"], code="not_staff"
            )


class GetUserForm(MisagoAuthMixin, forms.Form):
    email = forms.CharField()

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

        email = data.get("email")
        if not email or len(email) > 250:
            raise forms.ValidationError(_("Enter e-mail address."), code="empty_email")

        try:
            validate_email(email)
        except forms.ValidationError:
            raise forms.ValidationError(
                _("Entered e-mail is invalid."), code="invalid_email"
            )

        try:
            user = User.objects.get_by_email(data["email"])
            if not user.is_active:
                raise User.DoesNotExist()
            self.user_cache = user
        except User.DoesNotExist:
            raise forms.ValidationError(
                _("No user with this e-mail exists."), code="not_found"
            )

        self.confirm_allowed(user)

        return data

    def confirm_allowed(self, user):
        """override this method to include additional checks"""


class ResendActivationForm(GetUserForm):
    def confirm_allowed(self, user):
        username_format = {"user": user.username}

        if not user.requires_activation:
            message = _("%(user)s, your account is already active.")
            raise forms.ValidationError(
                message % username_format, code="already_active"
            )

        if user.requires_activation_by_admin:
            message = _("%(user)s, only administrator may activate your account.")
            raise forms.ValidationError(
                message % username_format, code="inactive_admin"
            )


class ResetPasswordForm(GetUserForm):
    error_messages = {
        "inactive_user": _(
            "You have to activate your account before "
            "you will be able to request new password."
        ),
        "inactive_admin": _(
            "Administrator has to activate your account before "
            "you will be able to request new password."
        ),
    }

    def confirm_allowed(self, user):
        self.confirm_user_active(user)