from __future__ import annotations

from typing import ClassVar

from django.db import models
from django.utils.translation import gettext_lazy as _

from simorgh.apps.hr_core.models.choices import AccrualMethod, CalendarType, LeaveRequestStatus
from simorgh.apps.hr_core.models.employee import Employee
from simorgh.core.models import (
    ScopedManager,
    TenantScopedModel,
    TenantWideScopedManager,
    UUIDModel,
)


class LeaveType(TenantScopedModel):
    """Tenant-wide leave type (annual, sick, unpaid, etc.).

    ``organization_node`` is NULL — leave types are tenant-wide reference data.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="leave_types",
        verbose_name=_("tenant"),
    )
    organization_node = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("organization node"),
    )

    objects = TenantWideScopedManager()

    name = models.CharField(_("name"), max_length=120)
    code = models.CharField(_("code"), max_length=40)
    color = models.CharField(_("color"), max_length=20, blank=True, default="#4A90D9")
    is_paid = models.BooleanField(_("paid"), default=True)
    requires_approval = models.BooleanField(_("requires approval"), default=True)
    max_days_per_year = models.DecimalField(
        _("max days per year"), max_digits=6, decimal_places=2, null=True, blank=True,
    )
    carry_forward_days = models.DecimalField(
        _("carry-forward days"), max_digits=6, decimal_places=2, default=0,
    )
    accrual_method = models.CharField(
        _("accrual method"), max_length=20, choices=AccrualMethod.choices,
        default=AccrualMethod.UPFRONT,
    )

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_leavetype"
        verbose_name = _("Leave Type")
        verbose_name_plural = _("Leave Types")
        constraints: ClassVar[list] = [
            models.UniqueConstraint(
                fields=["tenant", "code"],
                name="hr_leavetype_unique_tenant_code",
            )
        ]
        ordering = ["name"]

    def __str__(self) -> str:
        return self.name


class PublicHoliday(TenantScopedModel):
    """Tenant-wide public holiday calendar entry.

    ``organization_node`` is NULL — holidays are tenant-wide reference data.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="public_holidays",
        verbose_name=_("tenant"),
    )
    organization_node = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="+",
        verbose_name=_("organization node"),
    )

    objects = TenantWideScopedManager()

    date = models.DateField(_("date"))
    name = models.CharField(_("name"), max_length=200)
    calendar_type = models.CharField(
        _("calendar type"), max_length=20, choices=CalendarType.choices,
        default=CalendarType.GREGORIAN,
    )
    is_recurring = models.BooleanField(
        _("recurring annually"), default=False,
        help_text=_("If True, this holiday repeats on the same date each year."),
    )

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_publicholiday"
        verbose_name = _("Public Holiday")
        verbose_name_plural = _("Public Holidays")
        constraints: ClassVar[list] = [
            models.UniqueConstraint(
                fields=["tenant", "date", "calendar_type"],
                name="hr_publicholiday_unique_tenant_date_calendar",
            )
        ]
        ordering = ["date"]

    def __str__(self) -> str:
        return f"{self.name} ({self.date})"


class LeaveBalance(TenantScopedModel):
    """Annual leave balance for an employee per leave type.

    ``organization_node`` is populated from the employee at creation time.
    """

    employee = models.ForeignKey(
        Employee, on_delete=models.CASCADE, related_name="leave_balances",
        verbose_name=_("employee"),
    )
    leave_type = models.ForeignKey(
        LeaveType, on_delete=models.CASCADE, related_name="balances",
        verbose_name=_("leave type"),
    )
    year = models.PositiveSmallIntegerField(_("year"))
    allocated = models.DecimalField(_("allocated days"), max_digits=6, decimal_places=2, default=0)
    used = models.DecimalField(_("used days"), max_digits=6, decimal_places=2, default=0)
    pending = models.DecimalField(_("pending days"), max_digits=6, decimal_places=2, default=0)

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_leavebalance"
        verbose_name = _("Leave Balance")
        verbose_name_plural = _("Leave Balances")
        constraints: ClassVar[list] = [
            models.UniqueConstraint(
                fields=["employee", "leave_type", "year"],
                name="hr_leavebalance_unique_employee_type_year",
            )
        ]
        ordering = ["-year", "leave_type__name"]

    @property
    def remaining(self):
        return self.allocated - self.used - self.pending

    def __str__(self) -> str:
        return f"{self.employee} — {self.leave_type} — {self.year}"


class LeaveRequest(UUIDModel, TenantScopedModel):
    """A leave request submitted by an employee.

    ``organization_node`` is populated from the employee at creation time.
    """

    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        related_name="leave_requests",
        verbose_name=_("tenant"),
    )
    organization_node = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.PROTECT,
        related_name="+",
        verbose_name=_("organization node"),
    )

    employee = models.ForeignKey(
        Employee, on_delete=models.CASCADE, related_name="leave_requests",
        verbose_name=_("employee"),
    )
    leave_type = models.ForeignKey(
        LeaveType, on_delete=models.PROTECT, related_name="requests",
        verbose_name=_("leave type"),
    )
    from_date = models.DateField(_("from date"))
    to_date = models.DateField(_("to date"))
    days_requested = models.DecimalField(
        _("days requested"), max_digits=6, decimal_places=2, default=0,
    )
    reason = models.TextField(_("reason"), blank=True)
    status = models.CharField(
        _("status"), max_length=20, choices=LeaveRequestStatus.choices,
        default=LeaveRequestStatus.DRAFT,
    )
    submitted_at = models.DateTimeField(_("submitted at"), null=True, blank=True)
    reviewed_by = models.ForeignKey(
        "accounts.User", on_delete=models.SET_NULL, null=True, blank=True,
        related_name="reviewed_leave_requests", verbose_name=_("reviewed by"),
    )
    reviewed_at = models.DateTimeField(_("reviewed at"), null=True, blank=True)
    review_note = models.TextField(_("review note"), blank=True)

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_leaverequest"
        verbose_name = _("Leave Request")
        verbose_name_plural = _("Leave Requests")
        indexes: ClassVar[list[models.Index]] = [
            *TenantScopedModel.Meta.indexes,
            models.Index(fields=["tenant", "employee", "status"], name="hr_leavereq_tenant_emp_status"),
            models.Index(fields=["tenant", "status", "from_date"], name="hr_leavereq_tenant_status_date"),
            models.Index(fields=["employee", "leave_type", "status"], name="hr_leavereq_emp_type_status"),
        ]
        ordering = ["-created_at"]

    def __str__(self) -> str:
        return f"{self.employee} — {self.leave_type} ({self.from_date} → {self.to_date})"
