import uuid
from decimal import Decimal

from django.db import models

from apps.accounts.models import Company, TimeStampedModel, User

from .statutory import DEFAULT_PAYE_BANDS


def default_paye_bands():
    return list(DEFAULT_PAYE_BANDS)


class PayrollRates(TimeStampedModel):
    """A company's editable statutory rates. Seeded from the defaults, then
    owned by the user — statutory rates change and must not need a code
    change to keep up."""

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = models.OneToOneField(Company, on_delete=models.CASCADE, related_name='payroll_rates')
    # [{"up_to": "24000", "rate": "0.10"}, ..., {"up_to": null, "rate": "0.35"}]
    paye_bands = models.JSONField(default=default_paye_bands)
    personal_relief = models.DecimalField(max_digits=12, decimal_places=2, default=2400)
    nssf_rate = models.DecimalField(max_digits=6, decimal_places=4, default=Decimal('0.06'))
    nssf_tier_1_limit = models.DecimalField(max_digits=12, decimal_places=2, default=7000)
    nssf_tier_2_limit = models.DecimalField(max_digits=12, decimal_places=2, default=36000)
    shif_rate = models.DecimalField(max_digits=6, decimal_places=4, default=Decimal('0.0275'))
    shif_minimum = models.DecimalField(max_digits=12, decimal_places=2, default=300)
    housing_levy_rate = models.DecimalField(max_digits=6, decimal_places=4, default=Decimal('0.015'))
    nssf_deductible_for_paye = models.BooleanField(default=True)
    shif_deductible_for_paye = models.BooleanField(default=True)
    housing_levy_deductible_for_paye = models.BooleanField(default=True)

    class Meta:
        verbose_name_plural = 'payroll rates'

    def __str__(self):
        return f'Payroll rates - {self.company.name}'


class Employee(TimeStampedModel):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees')
    full_name = models.CharField(max_length=255)
    staff_number = models.CharField(max_length=64, blank=True)
    position = models.CharField(max_length=255, blank=True)
    email = models.EmailField(blank=True)
    phone = models.CharField(max_length=50, blank=True)
    kra_pin = models.CharField(max_length=32, blank=True)
    nssf_number = models.CharField(max_length=64, blank=True)
    shif_number = models.CharField(max_length=64, blank=True)
    basic_salary = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    allowances = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    bank_name = models.CharField(max_length=255, blank=True)
    bank_account = models.CharField(max_length=64, blank=True)
    hire_date = models.DateField(null=True, blank=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ['full_name']

    def __str__(self):
        return self.full_name

    @property
    def gross_pay(self):
        return self.basic_salary + self.allowances


class PayrollRun(TimeStampedModel):
    class Status(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        FINALISED = 'finalised', 'Finalised'

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='payroll_runs')
    period_year = models.PositiveIntegerField()
    period_month = models.PositiveSmallIntegerField()
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.DRAFT)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name='payroll_runs')
    employee_count = models.PositiveIntegerField(default=0)
    total_gross = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    total_deductions = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    total_net = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    # The exact rates this run was calculated with, so an old payslip stays
    # explainable after the rates are edited.
    rates_snapshot = models.JSONField(default=dict, blank=True)

    class Meta:
        ordering = ['-period_year', '-period_month']
        unique_together = ('company', 'period_year', 'period_month')

    def __str__(self):
        return f'{self.period_year}-{self.period_month:02d}'


class Payslip(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    run = models.ForeignKey(PayrollRun, on_delete=models.CASCADE, related_name='payslips')
    employee = models.ForeignKey(Employee, on_delete=models.SET_NULL, null=True, related_name='payslips')
    # Snapshot: a payslip is a historical record and must not change if the
    # employee is later renamed or removed.
    employee_name = models.CharField(max_length=255)
    basic_salary = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    allowances = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    gross_pay = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    taxable_income = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    paye = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    nssf = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    shif = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    housing_levy = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    other_deductions = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    total_deductions = models.DecimalField(max_digits=14, decimal_places=2, default=0)
    net_pay = models.DecimalField(max_digits=14, decimal_places=2, default=0)

    class Meta:
        ordering = ['employee_name']

    def __str__(self):
        return f'{self.employee_name} - {self.run}'
