from pathlib import Path

from django import forms
from django.utils import timezone
from .models import CustomFieldDefinition, CustomFieldValue, Employee, EmployeeDocument, EmployeePaymentAccount
from apps.organization.models import Branch, Department, Designation, Shift


class EmployeeForm(forms.ModelForm):
    class Meta:
        model = Employee
        fields = [
            "company", "employee_code", "full_name", "father_name", "mother_name",
            "date_of_birth", "gender", "marital_status", "blood_group", "nid_or_passport",
            "mobile", "email", "present_address", "permanent_address", "emergency_contact",
            "branch", "department", "designation", "shift", "reporting_manager",
            "employee_type", "joining_date", "confirmation_date", "status", "weekly_holiday",
            "ot_eligible", "photo",
        ]
        widgets = {
            "date_of_birth": forms.DateInput(attrs={"type": "date"}),
            "joining_date": forms.DateInput(attrs={"type": "date"}),
            "confirmation_date": forms.DateInput(attrs={"type": "date"}),
            "present_address": forms.Textarea(attrs={"rows": 2}),
            "permanent_address": forms.Textarea(attrs={"rows": 2}),
        }

    def __init__(self, *args, company_queryset=None, **kwargs):
        super().__init__(*args, **kwargs)
        if company_queryset is not None:
            self.fields["company"].queryset = company_queryset
        # Existing employee ownership/status are lifecycle-controlled. Moving an
        # employee to another company would leave payroll/attendance/loan/leave
        # history attached to the same employee and break company isolation.
        if getattr(self.instance, "pk", None):
            self.fields["company"].disabled = True
            self.fields["company"].help_text = "Employee company cannot be changed after creation."
            self.fields.pop("status", None)
        company_id = None
        if getattr(self.instance, "pk", None) and getattr(self.instance, "company_id", None):
            company_id = self.instance.company_id
        elif self.is_bound:
            company_id = self.data.get("company")
        elif getattr(self.instance, "company_id", None):
            company_id = self.instance.company_id
        if company_id:
            self.fields["branch"].queryset = Branch.objects.filter(company_id=company_id, is_active=True).order_by("name")
            self.fields["department"].queryset = Department.objects.filter(company_id=company_id, is_active=True).order_by("name")
            self.fields["designation"].queryset = Designation.objects.filter(company_id=company_id, is_active=True).order_by("name")
            self.fields["shift"].queryset = Shift.objects.filter(company_id=company_id, is_active=True).order_by("name")
            self.fields["reporting_manager"].queryset = Employee.objects.filter(company_id=company_id).exclude(pk=self.instance.pk).order_by("employee_code")
        else:
            self.fields["branch"].queryset = Branch.objects.none()
            self.fields["department"].queryset = Department.objects.none()
            self.fields["designation"].queryset = Designation.objects.none()
            self.fields["shift"].queryset = Shift.objects.none()
            self.fields["reporting_manager"].queryset = Employee.objects.none()

        self._custom_definitions = []
        if company_id:
            self._custom_definitions = list(CustomFieldDefinition.objects.filter(company_id=company_id, is_active=True).order_by("label", "pk"))
            existing = {}
            if getattr(self.instance, "pk", None):
                existing = {v.definition_id: v.value for v in self.instance.custom_values.select_related("definition").all()}
            for definition in self._custom_definitions:
                field_name = f"custom_{definition.key}"
                field_type = (definition.field_type or "text").lower()
                if field_type == "number":
                    field = forms.DecimalField(required=definition.is_required)
                elif field_type == "date":
                    field = forms.DateField(required=definition.is_required, widget=forms.DateInput(attrs={"type": "date"}))
                elif field_type in {"boolean", "checkbox"}:
                    field = forms.BooleanField(required=False)
                else:
                    field = forms.CharField(required=definition.is_required)
                field.label = definition.label
                if definition.pk in existing:
                    value = existing[definition.pk]
                    if field_type in {"boolean", "checkbox"}:
                        value = str(value).lower() in {"1", "true", "yes", "on"}
                    field.initial = value
                self.fields[field_name] = field

    def save(self, commit=True):
        employee = super().save(commit=commit)
        if commit and getattr(employee, "pk", None):
            for definition in getattr(self, "_custom_definitions", []):
                field_name = f"custom_{definition.key}"
                value = self.cleaned_data.get(field_name)
                if hasattr(value, "isoformat"):
                    value = value.isoformat()
                elif isinstance(value, bool):
                    value = "true" if value else "false"
                elif value is None:
                    value = ""
                else:
                    value = str(value)
                CustomFieldValue.objects.update_or_create(employee=employee, definition=definition, defaults={"value": value})
        return employee

    def clean(self):
        cleaned = super().clean()
        company = cleaned.get("company")
        for name in ("branch", "department", "designation", "shift"):
            obj = cleaned.get(name)
            if obj and company and obj.company_id != company.id:
                self.add_error(name, f"Selected {name.replace('_', ' ')} does not belong to the selected company.")
        manager = cleaned.get("reporting_manager")
        if manager and company and manager.company_id != company.id:
            self.add_error("reporting_manager", "Reporting manager must belong to the selected company.")
        if manager and self.instance.pk and manager.pk == self.instance.pk:
            self.add_error("reporting_manager", "An employee cannot report to themselves.")

        dob = cleaned.get("date_of_birth")
        joining = cleaned.get("joining_date")
        confirmation = cleaned.get("confirmation_date")
        if dob and dob > timezone.localdate():
            self.add_error("date_of_birth", "Date of birth cannot be in the future.")
        if dob and joining and dob >= joining:
            self.add_error("date_of_birth", "Date of birth must be earlier than the joining date.")
        if confirmation and joining and confirmation < joining:
            self.add_error("confirmation_date", "Confirmation date cannot be earlier than the joining date.")

        # Prevent indirect reporting loops (A -> B -> C -> A), not only self-reporting.
        if manager and self.instance.pk:
            cursor = manager
            visited = set()
            while cursor and cursor.pk not in visited:
                if cursor.pk == self.instance.pk:
                    self.add_error("reporting_manager", "Reporting manager selection would create a circular reporting hierarchy.")
                    break
                visited.add(cursor.pk)
                cursor = cursor.reporting_manager
        return cleaned


class EmployeeDocumentForm(forms.ModelForm):
    ALLOWED_EXTENSIONS = {".pdf", ".jpg", ".jpeg", ".png", ".doc", ".docx"}
    MAX_BYTES = 10 * 1024 * 1024

    class Meta:
        model = EmployeeDocument
        fields = ["document_type", "title", "file", "expires_on"]
        widgets = {"expires_on": forms.DateInput(attrs={"type": "date"})}

    def clean_file(self):
        upload = self.cleaned_data.get("file")
        if not upload:
            return upload
        if Path(upload.name or "").suffix.lower() not in self.ALLOWED_EXTENSIONS:
            raise forms.ValidationError("Unsupported document type. Allowed: PDF, JPG/PNG and Word documents.")
        if getattr(upload, "size", 0) > self.MAX_BYTES:
            raise forms.ValidationError("Employee document cannot exceed 10 MB.")
        return upload


class CustomFieldDefinitionForm(forms.ModelForm):
    FIELD_TYPE_CHOICES = (("text", "Text"), ("number", "Number"), ("date", "Date"), ("boolean", "Yes / No"))
    field_type = forms.ChoiceField(choices=FIELD_TYPE_CHOICES)

    class Meta:
        model = CustomFieldDefinition
        fields = ["company", "key", "label", "field_type", "is_required", "is_active"]

    def __init__(self, *args, company_queryset=None, **kwargs):
        super().__init__(*args, **kwargs)
        if company_queryset is not None:
            self.fields["company"].queryset = company_queryset
        if getattr(self.instance, "pk", None):
            self.fields["company"].disabled = True
            self.fields["company"].help_text = "Custom-field company cannot be changed after creation."

    def clean_key(self):
        value = (self.cleaned_data.get("key") or "").strip().lower()
        if not value:
            raise forms.ValidationError("Field key is required.")
        return value


class EmployeeLifecycleForm(forms.Form):
    ACTION_CHOICES = (
        ("CONFIRM", "Confirm Employment"),
        ("SUSPEND", "Suspend Employee"),
        ("REACTIVATE", "Reactivate Employee"),
    )
    action = forms.ChoiceField(choices=ACTION_CHOICES)
    effective_date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}), initial=timezone.localdate)
    reason = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3}))

    def __init__(self, *args, employee=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.employee = employee
        allowed = []
        if employee:
            if employee.status in {Employee.Status.ACTIVE, Employee.Status.PROBATION}:
                allowed.append(("CONFIRM", "Confirm Employment"))
            if employee.status in {Employee.Status.ACTIVE, Employee.Status.PROBATION, Employee.Status.CONFIRMED}:
                allowed.append(("SUSPEND", "Suspend Employee"))
            if employee.status in {Employee.Status.SUSPENDED, Employee.Status.INACTIVE}:
                allowed.append(("REACTIVATE", "Reactivate Employee"))
        self.fields["action"].choices = allowed

    def clean(self):
        cleaned = super().clean()
        if not self.employee:
            raise forms.ValidationError("Employee is required.")
        action = cleaned.get("action")
        effective = cleaned.get("effective_date")
        reason = (cleaned.get("reason") or "").strip()
        if effective and effective < self.employee.joining_date:
            self.add_error("effective_date", "Effective date cannot be earlier than joining date.")
        if action in {"SUSPEND", "REACTIVATE"} and len(reason) < 5:
            self.add_error("reason", "Please record a brief reason for this lifecycle change.")
        return cleaned


class EmployeePaymentAccountForm(forms.ModelForm):
    class Meta:
        model = EmployeePaymentAccount
        fields = [
            "payment_method", "account_name", "bank_name", "bank_branch", "account_number",
            "routing_number", "wallet_provider", "wallet_number", "is_active",
        ]

    def clean(self):
        cleaned = super().clean()
        method = cleaned.get("payment_method")
        if method == EmployeePaymentAccount.Method.BANK:
            for field_name in ("account_name", "bank_name", "account_number"):
                if not (cleaned.get(field_name) or "").strip():
                    self.add_error(field_name, "Required for bank-transfer salary payment.")
        elif method == EmployeePaymentAccount.Method.MFS:
            if not (cleaned.get("wallet_provider") or "").strip():
                self.add_error("wallet_provider", "Wallet provider is required for MFS payment.")
            if not (cleaned.get("wallet_number") or "").strip():
                self.add_error("wallet_number", "Wallet number is required for MFS payment.")
        return cleaned
