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 AssignmentType, PositionStatus
from simorgh.core.models import TenantScopedModel, UUIDModel, VersionedModel


class Position(UUIDModel, TenantScopedModel, VersionedModel):
    """A budgeted slot in the organization structure.

    Positions exist independently of employees. A position may be unfilled
    (vacant), filled by one employee (via PositionAssignment), or overfilled
    (multiple assignments when max_headcount > 1).
    """

    position_code = models.CharField(_("position code"), max_length=40)
    title = models.CharField(_("title"), max_length=200)
    job_title = models.ForeignKey(
        "JobTitle",
        on_delete=models.PROTECT,
        related_name="positions",
        verbose_name=_("job title"),
    )
    department = models.ForeignKey(
        "organizations.OrganizationNode",
        on_delete=models.PROTECT,
        related_name="positions",
        verbose_name=_("department"),
    )
    job_grade = models.ForeignKey(
        "JobGrade",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="positions",
        verbose_name=_("job grade"),
    )
    reports_to_position = models.ForeignKey(
        "self",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="subordinate_positions",
        verbose_name=_("reports to"),
    )
    is_budgeted = models.BooleanField(_("budgeted"), default=True)
    fte = models.DecimalField(
        _("FTE"),
        max_digits=3,
        decimal_places=2,
        default=1.0,
        help_text=_("Full-time equivalent. 1.0 = full-time, 0.5 = half-time."),
    )
    max_headcount = models.PositiveSmallIntegerField(
        _("max headcount"),
        default=1,
        help_text=_("Maximum number of employees that can hold this position."),
    )
    status = models.CharField(
        _("status"),
        max_length=20,
        choices=PositionStatus.choices,
        default=PositionStatus.PLANNED,
    )
    valid_from = models.DateField(_("valid from"))
    valid_to = models.DateField(_("valid to"), null=True, blank=True)
    description = models.TextField(_("description"), blank=True)

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_position"
        verbose_name = _("Position")
        verbose_name_plural = _("Positions")
        constraints: ClassVar[list] = [
            models.UniqueConstraint(
                fields=["tenant", "position_code"],
                name="hr_position_unique_tenant_code",
            )
        ]
        indexes: ClassVar[list[models.Index]] = [
            *TenantScopedModel.Meta.indexes,
            models.Index(fields=["tenant", "department"], name="hr_position_tenant_dept"),
            models.Index(fields=["tenant", "status"], name="hr_position_tenant_status"),
        ]
        ordering = ["department", "title"]

    def __str__(self) -> str:
        return f"{self.position_code} — {self.title}"

    @property
    def is_vacant(self) -> bool:
        return self.current_headcount == 0

    @property
    def current_headcount(self) -> int:
        return self.assignments.filter(end_date__isnull=True).count()


class PositionAssignment(UUIDModel, TenantScopedModel):
    """Links an employee to a position for a date range."""

    position = models.ForeignKey(
        Position,
        on_delete=models.CASCADE,
        related_name="assignments",
        verbose_name=_("position"),
    )
    employee = models.ForeignKey(
        "Employee",
        on_delete=models.CASCADE,
        related_name="position_assignments",
        verbose_name=_("employee"),
    )
    start_date = models.DateField(_("start date"))
    end_date = models.DateField(_("end date"), null=True, blank=True)
    is_primary = models.BooleanField(_("primary assignment"), default=True)
    assignment_type = models.CharField(
        _("type"),
        max_length=20,
        choices=AssignmentType.choices,
        default=AssignmentType.PERMANENT,
    )

    class Meta(TenantScopedModel.Meta):
        db_table = "hr_position_assignment"
        verbose_name = _("Position Assignment")
        verbose_name_plural = _("Position Assignments")
        constraints: ClassVar[list] = [
            models.UniqueConstraint(
                fields=["position", "employee"],
                condition=models.Q(is_primary=True),
                name="hr_posassign_unique_primary_per_employee",
            )
        ]
        ordering = ["-is_primary", "start_date"]

    def __str__(self) -> str:
        return f"{self.employee} → {self.position}"
