from django.db import models
from django.db.models import Q
from apps.core.models import ActiveModel

class Company(ActiveModel):
    code = models.CharField(max_length=30, unique=True)
    name = models.CharField(max_length=200)
    legal_name = models.CharField(max_length=250, blank=True)
    address = models.TextField(blank=True)
    currency_code = models.CharField(max_length=3, default="BDT")
    timezone = models.CharField(max_length=64, default="Asia/Dhaka")
    def __str__(self): return self.name

class Branch(ActiveModel):
    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="branches")
    code = models.CharField(max_length=30)
    name = models.CharField(max_length=200)
    address = models.TextField(blank=True)
    class Meta:
        constraints = [models.UniqueConstraint(fields=["company", "code"], name="uq_branch_company_code")]
    def __str__(self): return f"{self.company.code}/{self.code} - {self.name}"

class Department(ActiveModel):
    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="departments")
    code = models.CharField(max_length=30)
    name = models.CharField(max_length=150)
    class Meta:
        constraints = [models.UniqueConstraint(fields=["company", "code"], name="uq_department_company_code")]
    def __str__(self): return self.name

class Designation(ActiveModel):
    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="designations")
    code = models.CharField(max_length=30)
    name = models.CharField(max_length=150)
    class Meta:
        constraints = [models.UniqueConstraint(fields=["company", "code"], name="uq_designation_company_code")]
    def __str__(self): return self.name

class Shift(ActiveModel):
    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="shifts")
    code = models.CharField(max_length=30)
    name = models.CharField(max_length=150)
    start_time = models.TimeField()
    end_time = models.TimeField()
    grace_minutes = models.PositiveIntegerField(default=0)
    break_minutes = models.PositiveIntegerField(default=0)
    class Meta:
        constraints = [models.UniqueConstraint(fields=["company", "code"], name="uq_shift_company_code")]
    def __str__(self): return self.name

class Holiday(ActiveModel):
    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="holidays")
    holiday_date = models.DateField()
    name = models.CharField(max_length=150)
    branch = models.ForeignKey(Branch, on_delete=models.PROTECT, null=True, blank=True, related_name="holidays")
    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["company", "holiday_date"],
                condition=Q(branch__isnull=True),
                name="uq_holiday_company_date_all_branches",
            ),
            models.UniqueConstraint(
                fields=["company", "holiday_date", "branch"],
                condition=Q(branch__isnull=False),
                name="uq_holiday_company_date_branch",
            ),
        ]


class CompanyPolicy(ActiveModel):
    """Versioned business policy consumed by attendance/payroll services.

    Policy rows are effective-dated so a later change cannot silently rewrite
    historical payroll behavior. Previous policies stay in the database.
    """

    class ProrationMethod(models.TextChoices):
        ACTUAL_CALENDAR = "ACTUAL_CALENDAR", "Actual calendar days"
        FIXED_DIVISOR = "FIXED_DIVISOR", "Fixed salary divisor"

    class MissingPunchAction(models.TextChoices):
        BLOCK = "BLOCK", "Block attendance finalization"
        ABSENT = "ABSENT", "Treat incomplete punch as absent"
        KEEP = "KEEP", "Keep recorded status for HR review"

    class LoanRecoveryStartRule(models.TextChoices):
        SAME_MONTH = "SAME_MONTH", "Disbursement month or later"
        NEXT_MONTH = "NEXT_MONTH", "Month after disbursement or later"

    company = models.ForeignKey(Company, on_delete=models.PROTECT, related_name="policies")
    effective_from = models.DateField()
    effective_to = models.DateField(null=True, blank=True)
    salary_day_divisor = models.DecimalField(max_digits=7, decimal_places=2, default=30)
    monthly_working_hours = models.DecimalField(max_digits=7, decimal_places=2, default=208)
    salary_proration_method = models.CharField(max_length=30, choices=ProrationMethod.choices, default=ProrationMethod.ACTUAL_CALENDAR)
    missing_workday_is_absent = models.BooleanField(default=True)
    missing_punch_action = models.CharField(max_length=20, choices=MissingPunchAction.choices, default=MissingPunchAction.BLOCK)
    half_day_deduction_fraction = models.DecimalField(max_digits=5, decimal_places=2, default=0.50)
    ot_minimum_minutes = models.PositiveIntegerField(default=30)
    ot_rounding_minutes = models.PositiveIntegerField(default=30)
    ot_default_rate_multiplier = models.DecimalField(max_digits=6, decimal_places=2, default=1.50)
    ot_weekly_off_multiplier = models.DecimalField(max_digits=6, decimal_places=2, default=2.00)
    ot_holiday_multiplier = models.DecimalField(max_digits=6, decimal_places=2, default=2.00)
    leave_manager_approval_required = models.BooleanField(default=True)
    payroll_segregation_required = models.BooleanField(default=True)
    loan_recovery_start_rule = models.CharField(max_length=20, choices=LoanRecoveryStartRule.choices, default=LoanRecoveryStartRule.NEXT_MONTH)
    request_reference_prefix = models.CharField(max_length=12, default="REQ")
    payroll_reference_prefix = models.CharField(max_length=12, default="PAY")
    loan_reference_prefix = models.CharField(max_length=12, default="LOAN")
    notes = models.TextField(blank=True)

    class Meta:
        ordering = ["company", "-effective_from", "-pk"]
        constraints = [
            models.UniqueConstraint(fields=["company", "effective_from"], name="uq_company_policy_effective_from"),
        ]

    def clean(self):
        from django.core.exceptions import ValidationError
        if self.effective_to and self.effective_to < self.effective_from:
            raise ValidationError("Policy effective_to cannot be earlier than effective_from.")
        if self.salary_day_divisor <= 0:
            raise ValidationError("Salary day divisor must be greater than zero.")
        if self.monthly_working_hours <= 0:
            raise ValidationError("Monthly working hours must be greater than zero.")
        if self.half_day_deduction_fraction < 0 or self.half_day_deduction_fraction > 1:
            raise ValidationError("Half-day deduction fraction must be between 0 and 1.")
        if self.ot_rounding_minutes <= 0:
            raise ValidationError("OT rounding minutes must be greater than zero.")
        for name in ("ot_default_rate_multiplier", "ot_weekly_off_multiplier", "ot_holiday_multiplier"):
            if getattr(self, name) <= 0:
                raise ValidationError(f"{name.replace('_', ' ').title()} must be greater than zero.")
        for name in ("request_reference_prefix", "payroll_reference_prefix", "loan_reference_prefix"):
            value = (getattr(self, name) or "").strip().upper()
            if not value or not value.replace("-", "").isalnum():
                raise ValidationError(f"{name.replace('_', ' ').title()} must contain only letters, numbers or hyphen.")
            setattr(self, name, value)

    def __str__(self):
        return f"{self.company.code} policy from {self.effective_from}"
