from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q

from apps.attendance.models import AttendancePeriod, OvertimeEntry
from apps.employees.models import Employee
from .models import Bonus, Deduction, EmployeeSalaryAssignment, PayrollAdjustment, PayrollPeriod


class OvertimeEntryForm(forms.ModelForm):
    class Meta:
        model = OvertimeEntry
        fields = ["employee", "work_date", "requested_minutes", "rate_multiplier", "reason"]
        widgets = {
            "work_date": forms.DateInput(attrs={"type": "date"}),
            "reason": forms.Textarea(attrs={"rows": 3}),
        }

    def __init__(self, *args, employee_queryset=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["employee"].queryset = employee_queryset if employee_queryset is not None else Employee.objects.none()
        if getattr(self.instance, "pk", None):
            self.fields["employee"].disabled = True
            self.fields["employee"].help_text = "Employee cannot be changed after an approval task has been created."

    def clean(self):
        cleaned = super().clean()
        employee = cleaned.get("employee")
        minutes = cleaned.get("requested_minutes") or 0
        if employee and not employee.ot_eligible:
            raise ValidationError("This employee is not marked as OT eligible.")
        work_date = cleaned.get("work_date")
        if employee and work_date:
            from apps.attendance.services import employee_in_service_on
            if not employee_in_service_on(employee, work_date):
                self.add_error("work_date", "OT date must fall within the employee service period.")
            if AttendancePeriod.objects.filter(
                company=employee.company, year=work_date.year, month=work_date.month,
                status=AttendancePeriod.Status.FINALIZED,
            ).exists():
                self.add_error("work_date", "Attendance is finalized for this month. Reopen the attendance period before adding or editing OT.")
        if minutes <= 0:
            raise ValidationError("Requested OT minutes must be greater than zero.")
        return cleaned


class BonusForm(forms.ModelForm):
    class Meta:
        model = Bonus
        fields = ["employee", "payroll_month", "bonus_type", "amount", "reason"]
        widgets = {
            "payroll_month": forms.DateInput(attrs={"type": "date"}),
            "reason": forms.Textarea(attrs={"rows": 3}),
        }

    def __init__(self, *args, employee_queryset=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["employee"].queryset = employee_queryset if employee_queryset is not None else Employee.objects.none()
        if getattr(self.instance, "pk", None):
            self.fields["employee"].disabled = True
            self.fields["employee"].help_text = "Employee cannot be changed after an approval task has been created."

    def clean_payroll_month(self):
        value = self.cleaned_data["payroll_month"]
        return value.replace(day=1)

    def clean(self):
        cleaned = super().clean()
        employee = cleaned.get("employee")
        payroll_month = cleaned.get("payroll_month")
        if employee and payroll_month:
            period = PayrollPeriod.objects.filter(
                company=employee.company, year=payroll_month.year, month=payroll_month.month
            ).first()
            if period and period.status != PayrollPeriod.Status.DRAFT:
                self.add_error(
                    "payroll_month",
                    "This payroll month is already Reviewed/Approved/Locked. Use the controlled adjustment/arrear process instead.",
                )
        return cleaned

    def clean_amount(self):
        value = self.cleaned_data["amount"]
        if value <= 0:
            raise ValidationError("Bonus amount must be greater than zero.")
        return value


class DeductionForm(forms.ModelForm):
    class Meta:
        model = Deduction
        fields = ["employee", "payroll_month", "deduction_type", "amount", "reason"]
        widgets = {
            "payroll_month": forms.DateInput(attrs={"type": "date"}),
            "reason": forms.Textarea(attrs={"rows": 3}),
        }

    def __init__(self, *args, employee_queryset=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["employee"].queryset = employee_queryset if employee_queryset is not None else Employee.objects.none()
        if getattr(self.instance, "pk", None):
            self.fields["employee"].disabled = True
            self.fields["employee"].help_text = "Employee cannot be changed after an approval task has been created."

    def clean_payroll_month(self):
        value = self.cleaned_data["payroll_month"]
        return value.replace(day=1)

    def clean(self):
        cleaned = super().clean()
        employee = cleaned.get("employee")
        payroll_month = cleaned.get("payroll_month")
        if employee and payroll_month:
            period = PayrollPeriod.objects.filter(
                company=employee.company, year=payroll_month.year, month=payroll_month.month
            ).first()
            if period and period.status != PayrollPeriod.Status.DRAFT:
                self.add_error(
                    "payroll_month",
                    "This payroll month is already Reviewed/Approved/Locked. Use the controlled adjustment/arrear process instead.",
                )
        return cleaned

    def clean_amount(self):
        value = self.cleaned_data["amount"]
        if value <= 0:
            raise ValidationError("Deduction amount must be greater than zero.")
        return value


class SalaryAssignmentForm(forms.ModelForm):
    class Meta:
        model = EmployeeSalaryAssignment
        fields = [
            "employee", "structure", "effective_from", "effective_to",
            "gross_salary", "basic_salary", "ot_eligible", "ot_rate_multiplier", "notes",
        ]
        widgets = {
            "effective_from": forms.DateInput(attrs={"type": "date"}),
            "effective_to": forms.DateInput(attrs={"type": "date"}),
            "notes": forms.Textarea(attrs={"rows": 3}),
        }

    def __init__(self, *args, employee_queryset=None, company_ids=None, **kwargs):
        super().__init__(*args, **kwargs)
        if employee_queryset is not None:
            self.fields["employee"].queryset = employee_queryset
        if company_ids is not None:
            self.fields["structure"].queryset = self.fields["structure"].queryset.filter(company_id__in=company_ids)

    def clean(self):
        cleaned = super().clean()
        start = cleaned.get("effective_from")
        end = cleaned.get("effective_to")
        gross = cleaned.get("gross_salary")
        basic = cleaned.get("basic_salary")
        if start and end and end < start:
            raise ValidationError("Effective-to date cannot be earlier than effective-from date.")
        if gross is not None and gross <= 0:
            raise ValidationError("Gross salary must be greater than zero.")
        if basic is not None and basic < 0:
            raise ValidationError("Basic salary cannot be negative.")
        if gross is not None and basic is not None and basic > gross:
            raise ValidationError("Basic salary cannot be greater than gross salary.")

        employee = cleaned.get("employee")
        structure = cleaned.get("structure")
        if employee and structure and structure.company_id != employee.company_id:
            self.add_error("structure", "Salary structure must belong to the employee's company.")

        if employee and start and start < employee.joining_date:
            self.add_error("effective_from", "Salary effective-from date cannot be earlier than the employee joining date.")

        if employee and start:
            overlaps = EmployeeSalaryAssignment.objects.filter(employee=employee).exclude(pk=self.instance.pk)
            if end:
                overlaps = overlaps.filter(effective_from__lte=end).filter(
                    Q(effective_to__isnull=True) | Q(effective_to__gte=start)
                )
            else:
                overlaps = overlaps.filter(Q(effective_to__isnull=True) | Q(effective_to__gte=start))
            if overlaps.exists():
                raise ValidationError("Salary assignment effective dates overlap an existing assignment for this employee.")
        return cleaned


class PayrollPeriodForm(forms.ModelForm):
    def __init__(self, *args, company_ids=None, **kwargs):
        super().__init__(*args, **kwargs)
        if company_ids is not None:
            self.fields["company"].queryset = self.fields["company"].queryset.filter(pk__in=company_ids)

    class Meta:
        model = PayrollPeriod
        fields = ["company", "year", "month"]

    def clean_month(self):
        month = self.cleaned_data["month"]
        if month < 1 or month > 12:
            raise ValidationError("Month must be between 1 and 12.")
        return month


class PayrollAdjustmentForm(forms.ModelForm):
    class Meta:
        model = PayrollAdjustment
        fields = ["adjustment_type", "amount", "reason", "reference_no"]
        widgets = {"reason": forms.Textarea(attrs={"rows": 3})}

    def clean_adjustment_type(self):
        value = (self.cleaned_data.get("adjustment_type") or "").strip().upper()
        allowed = {"EARNING", "DEDUCTION"}
        if value not in allowed:
            raise ValidationError("Adjustment type must be EARNING or DEDUCTION.")
        return value

    def clean_amount(self):
        value = self.cleaned_data["amount"]
        if value <= 0:
            raise ValidationError("Adjustment amount must be greater than zero.")
        return value

    def clean_reason(self):
        value = (self.cleaned_data.get("reason") or "").strip()
        if len(value) < 5:
            raise ValidationError("Please provide a meaningful adjustment reason.")
        return value


class PayrollUnlockForm(forms.Form):
    reason = forms.CharField(widget=forms.Textarea(attrs={"rows": 3}), min_length=5)
