from django import forms
from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.crypto import get_random_string
from django.utils.text import slugify

from core.permissions import all_capabilities, capability_label

from .models import Employee, GroupCapability, User
from .rbac import ROLE_GROUP_PREFIX


def _custom_groups_queryset():
    return Group.objects.exclude(name__startswith=ROLE_GROUP_PREFIX).order_by("name")


class LoginForm(forms.Form):
    username = forms.CharField(max_length=150)
    password = forms.CharField(widget=forms.PasswordInput)


class UserCreateForm(forms.ModelForm):
    employee = forms.ModelChoiceField(
        queryset=Employee.objects.none(),
        required=False,
        help_text='Optional. Select an employee to prefill account details.',
    )
    default_password = forms.CharField(
        required=False,
        widget=forms.PasswordInput(render_value=True),
        initial="133",
        help_text='Temporary password to share with the user. Default is 133.',
    )
    generate_password = forms.BooleanField(
        required=False,
        initial=False,
        help_text='Generate a secure temporary password when blank.',
    )
    groups = forms.ModelMultipleChoiceField(
        queryset=Group.objects.none(),
        required=False,
        help_text='Optional organizational groups (not RBAC role groups).',
    )

    class Meta:
        model = User
        fields = [
            'username',
            'first_name',
            'last_name',
            'email',
            'contact',
            'role',
            'is_active',
            'must_change_password',
        ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["username"].required = False
        self.fields["username"].widget = forms.HiddenInput()
        self.fields['role'].choices = User.assignable_role_choices()
        self.fields['groups'].queryset = _custom_groups_queryset()
        self.fields['employee'].queryset = Employee.objects.filter(
            is_active=True, user__isnull=True
        ).order_by("first_name", "last_name", "code")
        self.fields['must_change_password'].initial = True
        self.fields['is_active'].initial = True
        self.fields['must_change_password'].disabled = True
        self.created_password = ""
        self._resolved_password = ""
        self.order_fields(
            [
                "employee",
                "first_name",
                "last_name",
                "email",
                "contact",
                "role",
                "groups",
                "is_active",
                "must_change_password",
                "default_password",
                "generate_password",
            ]
        )

    def _temporary_password(self):
        # Prefix with mixed chars to satisfy default validators reliably.
        return f"Tmp@{get_random_string(10)}"

    def _generate_username_seed(self, cleaned_data, employee):
        if employee:
            seed = slugify(f"{employee.first_name} {employee.last_name}")
            if seed:
                return seed.replace("-", "")

        first_name = (cleaned_data.get("first_name") or "").strip()
        last_name = (cleaned_data.get("last_name") or "").strip()
        seed = slugify(f"{first_name} {last_name}")
        if seed:
            return seed.replace("-", "")

        email = (cleaned_data.get("email") or "").strip()
        if email and "@" in email:
            local_part = email.split("@", 1)[0]
            seed = slugify(local_part)
            if seed:
                return seed.replace("-", "")

        role = (cleaned_data.get("role") or "").strip()
        seed = slugify(role)
        if seed:
            return seed.replace("-", "")
        return "user"

    def _generate_unique_username(self, seed):
        base = (seed or "user")[:150]
        if not User.objects.filter(username=base).exists():
            return base

        index = 2
        while index < 10000:
            suffix = str(index)
            candidate = f"{base[: max(1, 150 - len(suffix))]}{suffix}"
            if not User.objects.filter(username=candidate).exists():
                return candidate
            index += 1

        return f"user{User.objects.count() + 1}"

    def clean(self):
        cleaned_data = super().clean()
        employee = cleaned_data.get("employee")

        if employee and employee.user_id:
            self.add_error("employee", "This employee already has a user account.")

        if employee:
            cleaned_data["first_name"] = cleaned_data.get("first_name") or employee.first_name
            cleaned_data["last_name"] = cleaned_data.get("last_name") or employee.last_name
            cleaned_data["email"] = cleaned_data.get("email") or employee.email
            cleaned_data["contact"] = cleaned_data.get("contact") or employee.phone
            cleaned_data["role"] = cleaned_data.get("role") or employee.role
            self.cleaned_data.update(
                {
                    "first_name": cleaned_data["first_name"],
                    "last_name": cleaned_data["last_name"],
                    "email": cleaned_data["email"],
                    "contact": cleaned_data["contact"],
                    "role": cleaned_data["role"],
                }
            )

        generated_username = self._generate_unique_username(
            self._generate_username_seed(cleaned_data, employee)
        )
        cleaned_data["username"] = generated_username
        self.cleaned_data["username"] = generated_username

        password = (cleaned_data.get("default_password") or "").strip()
        if not password:
            password = self._temporary_password() if cleaned_data.get("generate_password") else "133"

        validator_user = User(
            username=cleaned_data.get("username") or "",
            first_name=cleaned_data.get("first_name") or "",
            last_name=cleaned_data.get("last_name") or "",
            email=cleaned_data.get("email") or "",
        )
        if password != "133":
            try:
                validate_password(password, user=validator_user)
            except DjangoValidationError as exc:
                self.add_error("default_password", exc)
                raise forms.ValidationError("Please provide a stronger temporary password.")

        self._resolved_password = password
        cleaned_data["must_change_password"] = True
        self.cleaned_data["must_change_password"] = True
        return cleaned_data

    def save(self, commit=True):
        groups = self.cleaned_data.get("groups")
        employee = self.cleaned_data.get("employee")
        user = super().save(commit=False)
        user.set_password(self._resolved_password)
        user.must_change_password = True
        if commit:
            user.save()
            if groups is not None:
                user.groups.add(*groups)
            if employee:
                employee.user = user
                employee.save(update_fields=["user"])
        self.created_password = self._resolved_password
        return user


class UserEditForm(forms.ModelForm):
    groups = forms.ModelMultipleChoiceField(
        queryset=Group.objects.none(),
        required=False,
        help_text='Optional organizational groups (not RBAC role groups).',
    )

    class Meta:
        model = User
        fields = [
            'username',
            'first_name',
            'last_name',
            'email',
            'contact',
            'role',
            'is_active',
            'must_change_password',
            'locked_until',
            'groups',
        ]
        widgets = {
            "locked_until": forms.DateTimeInput(attrs={"type": "datetime-local"}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['role'].choices = User.assignable_role_choices()
        self.fields['groups'].queryset = _custom_groups_queryset()
        if self.instance and self.instance.pk:
            self.fields['groups'].initial = self.instance.groups.exclude(
                name__startswith=ROLE_GROUP_PREFIX
            )
            if self.instance.locked_until:
                self.initial["locked_until"] = self.instance.locked_until.strftime(
                    "%Y-%m-%dT%H:%M"
                )

    def save(self, commit=True):
        groups = self.cleaned_data.get("groups")
        user = super().save(commit=False)
        if commit:
            user.save()
            if groups is not None:
                user.groups.remove(*user.groups.exclude(name__startswith=ROLE_GROUP_PREFIX))
                if groups:
                    user.groups.add(*groups)
        return user


class UserPasswordResetForm(forms.Form):
    new_password = forms.CharField(
        widget=forms.PasswordInput,
        validators=[validate_password],
    )
    confirm_password = forms.CharField(widget=forms.PasswordInput)
    must_change_password = forms.BooleanField(
        required=False,
        initial=True,
        help_text='Force user to change password at next login.',
    )

    def clean(self):
        cleaned_data = super().clean()
        p1 = cleaned_data.get('new_password')
        p2 = cleaned_data.get('confirm_password')
        if p1 and p2 and p1 != p2:
            raise forms.ValidationError('Passwords do not match.')
        return cleaned_data


class GroupForm(forms.ModelForm):
    users = forms.ModelMultipleChoiceField(
        queryset=User.objects.filter(is_active=True).order_by("username"),
        required=False,
        help_text='Users assigned to this organizational group.',
    )
    capabilities = forms.MultipleChoiceField(
        choices=[(cap, capability_label(cap)) for cap in all_capabilities()],
        required=False,
        help_text='Extra capabilities granted via this group.',
    )

    class Meta:
        model = Group
        fields = ["name"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance and self.instance.pk:
            self.fields["users"].initial = self.instance.user_set.order_by("username")
            self.fields["capabilities"].initial = GroupCapability.objects.filter(
                group=self.instance
            ).values_list("capability", flat=True)

    def clean_name(self):
        name = self.cleaned_data["name"].strip()
        if not name:
            raise forms.ValidationError("Group name is required.")
        if name.lower().startswith(ROLE_GROUP_PREFIX):
            raise forms.ValidationError("Names starting with 'role:' are reserved.")
        return name

    def save(self, commit=True):
        users = self.cleaned_data.get("users")
        capabilities = self.cleaned_data.get("capabilities", [])
        group = super().save(commit=commit)
        if commit:
            if users is not None:
                group.user_set.set(users)
            GroupCapability.objects.filter(group=group).exclude(
                capability__in=capabilities
            ).delete()
            existing = set(
                GroupCapability.objects.filter(group=group).values_list(
                    "capability", flat=True
                )
            )
            to_add = [cap for cap in capabilities if cap not in existing]
            if to_add:
                GroupCapability.objects.bulk_create(
                    [GroupCapability(group=group, capability=cap) for cap in to_add],
                    ignore_conflicts=True,
                )
        return group


class EmployeeForm(forms.ModelForm):
    class Meta:
        model = Employee
        fields = [
            "code",
            "first_name",
            "last_name",
            "email",
            "phone",
            "role",
            "is_active",
        ]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["role"].choices = User.assignable_role_choices()


class ChangePasswordForm(forms.Form):
    current_password = forms.CharField(widget=forms.PasswordInput)
    new_password = forms.CharField(
        widget=forms.PasswordInput,
        validators=[validate_password]
    )
    confirm_password = forms.CharField(widget=forms.PasswordInput)

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

    def clean_current_password(self):
        current = self.cleaned_data.get('current_password')
        if not self.user.check_password(current):
            raise forms.ValidationError('Current password is incorrect.')
        return current

    def clean(self):
        cleaned_data = super().clean()
        p1 = cleaned_data.get('new_password')
        p2 = cleaned_data.get('confirm_password')
        if p1 and p2 and p1 != p2:
            raise forms.ValidationError('New passwords do not match.')
        return cleaned_data
