import uuid
from decimal import Decimal

from django.db import models


def _branch_fk(null=False):
    """Reference to accounts.Branch (shared DB) - by id only, no DB constraint."""
    return models.ForeignKey(
        "accounts.Branch", on_delete=models.DO_NOTHING, null=null, blank=null,
        db_constraint=False, related_name="+",
    )


def _company_fk(related_name):
    return models.ForeignKey(
        "accounts.Company", on_delete=models.CASCADE, db_constraint=False, related_name=related_name,
    )


def _user_fk():
    return models.ForeignKey(
        "accounts.CustomUser", on_delete=models.DO_NOTHING, null=True, blank=True,
        db_constraint=False, related_name="+",
    )


ZERO = Decimal("0")

WEEKDAY_CHOICES = (
    ("0", "Monday"), ("1", "Tuesday"), ("2", "Wednesday"), ("3", "Thursday"),
    ("4", "Friday"), ("5", "Saturday"), ("6", "Sunday"),
)


class PayrollSetting(models.Model):
    """Company-wide payroll rules (one row per company)."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = models.OneToOneField(
        "accounts.Company", on_delete=models.CASCADE, db_constraint=False, related_name="payroll_setting",
    )
    weekly_off_days = models.CharField(
        max_length=20, default="4",
        help_text="Comma separated weekday numbers, Monday=0 ... Sunday=6. Default 4 = Friday.",
    )
    work_hours_per_day = models.DecimalField(max_digits=4, decimal_places=1, default=Decimal("8"))
    overtime_multiplier = models.DecimalField(
        max_digits=4, decimal_places=2, default=Decimal("2.00"),
        help_text="Overtime is paid at this multiple of the normal hourly rate.",
    )
    late_days_per_absence = models.PositiveSmallIntegerField(
        default=3, help_text="Every N late days count as 1 absent day. 0 = never deduct for late arrival.",
    )
    unmarked_as_absent = models.BooleanField(
        default=False,
        help_text="If ticked, working days with no attendance record are treated as absent. "
                  "Otherwise they are treated as present.",
    )
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "payroll_settings"

    @property
    def weekly_off_set(self):
        out = set()
        for part in (self.weekly_off_days or "").split(","):
            part = part.strip()
            if part.isdigit() and 0 <= int(part) <= 6:
                out.add(int(part))
        return out


class Employee(models.Model):
    STATUS_ACTIVE = "active"
    STATUS_INACTIVE = "inactive"
    STATUS_RESIGNED = "resigned"
    STATUS_TERMINATED = "terminated"
    STATUS_CHOICES = (
        (STATUS_ACTIVE, "Active"),
        (STATUS_INACTIVE, "Inactive"),
        (STATUS_RESIGNED, "Resigned"),
        (STATUS_TERMINATED, "Terminated"),
    )
    GENDER_CHOICES = (("male", "Male"), ("female", "Female"), ("other", "Other"))
    PAY_METHOD_CHOICES = (
        ("cash", "Cash"), ("bank", "Bank Transfer"), ("bkash", "bKash"),
        ("nagad", "Nagad"), ("rocket", "Rocket"), ("other", "Other"),
    )

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = _company_fk("employees")
    branch = _branch_fk()
    employee_code = models.CharField(max_length=30)
    full_name = models.CharField(max_length=150)
    phone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address = models.TextField(blank=True)
    national_id = models.CharField("NID / ID number", max_length=30, blank=True)
    gender = models.CharField(max_length=10, choices=GENDER_CHOICES, blank=True)
    date_of_birth = models.DateField(null=True, blank=True)
    emergency_contact = models.CharField(max_length=150, blank=True, help_text="Name and phone number.")

    designation = models.CharField(max_length=100, blank=True)
    department = models.CharField(max_length=100, blank=True)
    join_date = models.DateField()
    left_date = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=12, choices=STATUS_CHOICES, default=STATUS_ACTIVE)

    basic_salary = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    house_rent = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    medical_allowance = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    transport_allowance = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    other_allowance = models.DecimalField(max_digits=12, decimal_places=2, default=0)

    payment_method = models.CharField(max_length=10, choices=PAY_METHOD_CHOICES, default="cash")
    account_details = models.CharField(
        max_length=150, blank=True, help_text="Bank name + account number, or the mobile wallet number.",
    )
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "employees"
        ordering = ["employee_code"]
        unique_together = [("company", "employee_code")]
        indexes = [models.Index(fields=["company", "branch", "status"])]

    def __str__(self):
        return f"{self.employee_code} - {self.full_name}"

    @property
    def gross_salary(self):
        return (
            (self.basic_salary or ZERO) + (self.house_rent or ZERO) + (self.medical_allowance or ZERO)
            + (self.transport_allowance or ZERO) + (self.other_allowance or ZERO)
        )

    @property
    def is_active_staff(self):
        return self.status == self.STATUS_ACTIVE


class Attendance(models.Model):
    PRESENT = "present"
    LATE = "late"
    ABSENT = "absent"
    HALF_DAY = "half_day"
    PAID_LEAVE = "paid_leave"
    UNPAID_LEAVE = "unpaid_leave"
    STATUS_CHOICES = (
        (PRESENT, "Present"),
        (LATE, "Late"),
        (ABSENT, "Absent"),
        (HALF_DAY, "Half Day"),
        (PAID_LEAVE, "Paid Leave"),
        (UNPAID_LEAVE, "Unpaid Leave"),
    )
    # short codes used on the monthly attendance sheet
    CODES = {
        PRESENT: "P", LATE: "L", ABSENT: "A", HALF_DAY: "\u00bd",
        PAID_LEAVE: "PL", UNPAID_LEAVE: "UL",
    }

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = _company_fk("attendances")
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="attendances")
    date = models.DateField()
    status = models.CharField(max_length=15, choices=STATUS_CHOICES)
    check_in = models.TimeField(null=True, blank=True)
    check_out = models.TimeField(null=True, blank=True)
    overtime_hours = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    note = models.CharField(max_length=200, blank=True)
    created_by = _user_fk()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "attendances"
        ordering = ["-date"]
        unique_together = [("employee", "date")]
        indexes = [models.Index(fields=["company", "date"])]

    def __str__(self):
        return f"{self.employee.full_name} {self.date} {self.status}"


class Holiday(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = _company_fk("holidays")
    branch = _branch_fk(null=True)      # empty = applies to every branch
    date = models.DateField()
    name = models.CharField(max_length=120)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "holidays"
        ordering = ["date"]
        indexes = [models.Index(fields=["company", "date"])]

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


class SalaryAdvance(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = _company_fk("salary_advances")
    branch = _branch_fk()
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="advances")
    date = models.DateField()
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    monthly_deduction = models.DecimalField(
        max_digits=12, decimal_places=2, null=True, blank=True,
        help_text="Amount recovered from each monthly salary. Leave empty to recover the full amount at once.",
    )
    recovered = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    reason = models.CharField(max_length=200, blank=True)
    created_by = _user_fk()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "salary_advances"
        ordering = ["-date", "-created_at"]
        indexes = [models.Index(fields=["company", "branch"])]

    def __str__(self):
        return f"{self.employee.full_name} advance {self.amount}"

    @property
    def outstanding(self):
        return max(ZERO, (self.amount or ZERO) - (self.recovered or ZERO))

    @property
    def next_installment(self):
        out = self.outstanding
        if self.monthly_deduction:
            return min(out, self.monthly_deduction)
        return out


class Payslip(models.Model):
    DRAFT = "draft"
    APPROVED = "approved"
    PAID = "paid"
    STATUS_CHOICES = ((DRAFT, "Draft"), (APPROVED, "Approved"), (PAID, "Paid"))

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    company = _company_fk("payslips")
    branch = _branch_fk()               # branch of the employee when the sheet was generated
    employee = models.ForeignKey(Employee, on_delete=models.PROTECT, related_name="payslips")
    year = models.PositiveSmallIntegerField()
    month = models.PositiveSmallIntegerField()

    # snapshot of the salary structure
    basic = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    house_rent = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    medical = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    transport = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    other_allowance = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    gross_salary = models.DecimalField(max_digits=12, decimal_places=2, default=0)

    # attendance summary
    days_in_month = models.PositiveSmallIntegerField(default=30)
    present_days = models.PositiveSmallIntegerField(default=0)
    late_days = models.PositiveSmallIntegerField(default=0)
    half_days = models.PositiveSmallIntegerField(default=0)
    absent_days = models.PositiveSmallIntegerField(default=0)
    paid_leave_days = models.PositiveSmallIntegerField(default=0)
    unpaid_leave_days = models.PositiveSmallIntegerField(default=0)
    weekly_off_days = models.PositiveSmallIntegerField(default=0)
    holiday_days = models.PositiveSmallIntegerField(default=0)
    unmarked_days = models.PositiveSmallIntegerField(default=0)
    not_employed_days = models.PositiveSmallIntegerField(default=0)
    deduction_days = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    overtime_hours = models.DecimalField(max_digits=6, decimal_places=2, default=0)

    # money
    absence_deduction = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    overtime_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    bonus = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    advance_deduction = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    other_deduction = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    net_salary = models.DecimalField(max_digits=12, decimal_places=2, default=0)

    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=DRAFT)
    paid_date = models.DateField(null=True, blank=True)
    payment_method = models.CharField(max_length=10, choices=Employee.PAY_METHOD_CHOICES, blank=True)
    payment_reference = models.CharField(max_length=100, blank=True)
    notes = models.CharField(max_length=255, blank=True)
    created_by = _user_fk()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "payslips"
        ordering = ["employee__employee_code"]
        unique_together = [("employee", "year", "month")]
        indexes = [
            models.Index(fields=["company", "year", "month"]),
            models.Index(fields=["company", "branch", "status", "paid_date"]),
        ]

    def __str__(self):
        return f"{self.employee.full_name} {self.year}-{self.month:02d}"

    @property
    def total_earnings(self):
        return (self.gross_salary or ZERO) + (self.overtime_amount or ZERO) + (self.bonus or ZERO)

    @property
    def total_deductions(self):
        return (self.absence_deduction or ZERO) + (self.advance_deduction or ZERO) + (self.other_deduction or ZERO)

    @property
    def salary_cost(self):
        """Cost of this payslip to the business. Advances are a prepayment of
        salary, so the amount recovered from the payslip is still salary cost."""
        return (self.net_salary or ZERO) + (self.advance_deduction or ZERO)

    @property
    def is_locked(self):
        return self.status != self.DRAFT
