from django.db import models
from django.conf import settings
from django.utils import timezone
import secrets
import time
from apps.core.uploads import tenant_upload_path


def uuid7() -> str:
    """Generate a UUID v7 (time-ordered UUID)."""
    nanoseconds = time.time_ns()
    uuid_int = (nanoseconds << 16) | secrets.randbits(48)
    return f"{uuid_int:032x}"


class Employee(models.Model):
    """Employee records linked to user accounts."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.ForeignKey("companies.Organization", on_delete=models.CASCADE, related_name="employees")
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="employee")
    employee_id = models.CharField(max_length=50)
    employment_type = models.CharField(max_length=20, choices=[
        ("staff", "Staff"),
        ("fellow", "Fellow"),
        ("intern", "Intern"),
    ], default="staff", db_index=True)
    position = models.CharField(max_length=100)
    department = models.CharField(max_length=100)
    manager = models.ForeignKey(
        "self", on_delete=models.SET_NULL, null=True, blank=True, related_name="direct_reports"
    )
    category = models.CharField(max_length=20, blank=True, help_text="Grade/rank code, e.g. III-C")
    phone_extension = models.CharField(max_length=10, blank=True, help_text="Internal extension, e.g. 530")
    status = models.CharField(max_length=20, choices=[
        ("active", "Active"),
        ("inactive", "Inactive"),
        ("on_leave", "On Leave"),
        ("terminated", "Terminated"),
    ], default="active", db_index=True)
    hire_date = models.DateField()
    termination_date = models.DateField(null=True, blank=True)
    salary = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    emergency_contact_name = models.CharField(max_length=100, blank=True)
    emergency_contact_phone = models.CharField(max_length=20, blank=True)
    address = models.TextField(blank=True)
    notes = models.TextField(blank=True)
    # Per-employee overrides for org payroll/work policy. Empty {} = inherit org defaults.
    # Shape: {"transport_mode": "fixed"|"daily", "transport_amount": 0,
    #         "remote_allowed": bool, "overtime_allowed": bool,
    #         "allowance_overrides": {"<key>": amount}, ...}
    payroll_overrides = models.JSONField(default=dict, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_employees"
        ordering = ["employee_id"]
        constraints = [models.UniqueConstraint(fields=["organization", "employee_id"], name="unique_employee_id_per_org")]

    def __str__(self):
        return f"{self.user.get_full_name()} ({self.employee_id})"


class Attendance(models.Model):
    """Employee attendance records."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="attendance_records")
    type = models.CharField(max_length=20, choices=[
        ("check_in", "Check In"),
        ("check_out", "Check Out"),
        ("absent", "Absent"),
        ("leave", "Leave"),
    ])
    date = models.DateField()
    time = models.TimeField(null=True, blank=True)
    location = models.CharField(max_length=100, blank=True)
    photo = models.ImageField(upload_to=tenant_upload_path("attendance"), null=True, blank=True)
    face_match_score = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

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

    def __str__(self):
        return f"{self.employee} - {self.type} - {self.date}"


class Payroll(models.Model):
    """Employee payroll records."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="payrolls")
    period_start = models.DateField()
    period_end = models.DateField()
    base_salary = models.DecimalField(max_digits=12, decimal_places=2)
    allowances = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    deductions = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    net_salary = models.DecimalField(max_digits=12, decimal_places=2)
    # Per-run line-item breakdown: {"earnings": [{label, amount}], "deductions": [{label, amount}], "meta": {...}}
    breakdown = models.JSONField(default=dict, blank=True)
    status = models.CharField(max_length=20, choices=[
        ("draft", "Draft"),
        ("pending", "Pending"),
        ("approved", "Approved"),
        ("paid", "Paid"),
    ], default="draft")
    approved_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
    paid_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "hr_payrolls"
        ordering = ["-period_start"]
        indexes = [
            # Cash statements filter paid payrolls on every request.
            models.Index(fields=["status"]),
            models.Index(fields=["period_start", "period_end"]),
        ]

    def __str__(self):
        return f"{self.employee} - {self.period_start} to {self.period_end}"


class Recruitment(models.Model):
    """Job recruitment/applications."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.ForeignKey("companies.Organization", on_delete=models.CASCADE, related_name="recruitments")
    position = models.CharField(max_length=200)
    department = models.CharField(max_length=100)
    employment_type = models.CharField(max_length=20, choices=[
        ("full_time", "Full Time"),
        ("part_time", "Part Time"),
        ("contract", "Contract"),
        ("internship", "Internship"),
    ], default="full_time")
    location = models.CharField(max_length=150, blank=True)
    salary_range = models.CharField(max_length=100, blank=True)
    deadline = models.DateField(null=True, blank=True, help_text="Application closing date")
    description = models.TextField()
    requirements = models.TextField()
    status = models.CharField(max_length=20, choices=[
        ("open", "Open"),
        ("closed", "Closed"),
        ("on_hold", "On Hold"),
    ], default="open")
    hiring_manager = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name="recruitments")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_recruitments"
        ordering = ["-created_at"]


class JobApplication(models.Model):
    """A candidate's application to a Recruitment posting (public submission)."""
    STAGE_CHOICES = [
        ("applied", "Applied"),
        ("screening", "Screening"),
        ("interview", "Interview"),
        ("offer", "Offer"),
        ("hired", "Hired"),
        ("rejected", "Rejected"),
    ]
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    recruitment = models.ForeignKey(Recruitment, on_delete=models.CASCADE, related_name="applications")
    full_name = models.CharField(max_length=150)
    email = models.EmailField()
    phone = models.CharField(max_length=30, blank=True)
    resume = models.FileField(upload_to=tenant_upload_path("applications"), null=True, blank=True)
    writing_sample = models.FileField(upload_to=tenant_upload_path("applications-writing"), null=True, blank=True)
    photo = models.ImageField(upload_to=tenant_upload_path("applications-photos"), null=True, blank=True)
    cover_letter = models.TextField(blank=True)
    stage = models.CharField(max_length=20, choices=STAGE_CHOICES, default="applied", db_index=True)
    notes = models.TextField(blank=True, help_text="Internal reviewer notes")
    # Set once a hired application is converted into an Employee record.
    converted_employee = models.OneToOneField(
        Employee, on_delete=models.SET_NULL, null=True, blank=True, related_name="source_application"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_job_applications"
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["recruitment", "stage"])]

    def __str__(self):
        return f"{self.full_name} → {self.recruitment.position}"


class Onboarding(models.Model):
    """Employee onboarding tasks and tracking."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.OneToOneField(Employee, on_delete=models.CASCADE, related_name="onboarding")
    stage = models.CharField(max_length=20, choices=[
        ("pending", "Pending"),
        ("in_progress", "In Progress"),
        ("completed", "Completed"),
    ], default="pending")
    start_date = models.DateField()
    end_date = models.DateField(null=True, blank=True)
    tasks = models.JSONField(default=list)
    checklist = models.JSONField(default=dict)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_onboardings"

    def __str__(self):
        return f"Onboarding: {self.employee}"


class Contract(models.Model):
    """Employee contracts."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="contracts")
    contract_type = models.CharField(max_length=20, choices=[
        ("full_time", "Full Time"),
        ("part_time", "Part Time"),
        ("contract", "Contract"),
        ("internship", "Internship"),
    ])
    start_date = models.DateField()
    end_date = models.DateField(null=True, blank=True)
    status = models.CharField(max_length=20, choices=[
        ("active", "Active"),
        ("expired", "Expired"),
        ("terminated", "Terminated"),
        ("renewed", "Renewed"),
    ], default="active")
    document = models.FileField(upload_to=tenant_upload_path("contracts"), null=True, blank=True)
    terms = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_contracts"
        ordering = ["-start_date"]

    def __str__(self):
        return f"Contract: {self.employee} - {self.contract_type}"


class EmployeeDocument(models.Model):
    """General-purpose employee documents (surat perjalanan dinas, ID copies, certificates, etc.)."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="documents")
    document_type = models.CharField(max_length=50, choices=[
        ("surat_perjalanan_dinas", "Surat Perjalanan Dinas"),
        ("surat_tugas", "Surat Tugas"),
        ("surat_keterangan", "Surat Keterangan"),
        ("id_card", "ID Card / KTP"),
        ("certificate", "Certificate"),
        ("cv", "CV / Resume"),
        ("other", "Other"),
    ], default="other")
    title = models.CharField(max_length=255)
    file = models.FileField(upload_to=tenant_upload_path("employee-documents"), null=True, blank=True)
    issue_date = models.DateField(null=True, blank=True)
    notes = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=[
        ("pending", "Pending"),
        ("approved", "Approved"),
        ("rejected", "Rejected"),
    ], default="approved")
    submitted_by_employee = models.BooleanField(default=False)
    reviewed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_employee_documents"
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["employee", "status"])]

    def __str__(self):
        return f"{self.title} - {self.employee}"


class LeaveRequest(models.Model):
    """Employee time-off requests (annual, sick, unpaid, etc.), submitted from mobile or web."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="leave_requests")
    # No `choices`: the valid set is per-organization, configured in
    # LeaveSettings, so a fixed list here would either forbid an org's own
    # configured type or hardcode types some orgs never use. Validation lives in
    # the serializer, which can see the caller's organization; a model-level
    # choices list cannot. Existing rows keep their keys — the shipped defaults
    # include annual/sick/unpaid/maternity/paternity for that reason.
    leave_type = models.CharField(max_length=32)
    start_date = models.DateField()
    end_date = models.DateField()
    reason = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=[
        ("pending", "Pending"),
        ("approved", "Approved"),
        ("rejected", "Rejected"),
    ], default="pending")
    reviewed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_leave_requests"
        ordering = ["-start_date"]
        indexes = [models.Index(fields=["employee", "status"])]

    def leave_type_label(self) -> str:
        """Configured label for this request's type, falling back to the key.

        Replaces get_leave_type_display(), which returned the human label only
        while leave_type had a fixed `choices` list. Without choices Django
        returns the raw key, so callers building user-facing text must go
        through the organization's configured types instead.
        """
        organization = getattr(self.employee, "organization", None)
        if organization is not None:
            types = (LeaveSettings.get_solo(organization).settings or {}).get("types", [])
            for entry in types:
                if entry.get("key") == self.leave_type:
                    return entry.get("label") or self.leave_type
        return self.leave_type.replace("_", " ").title()

    def __str__(self):
        return f"{self.leave_type_label()} - {self.employee}"


class BusinessTrip(models.Model):
    """Employee business/field trip requests, approved by HR."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="business_trips")
    title = models.CharField(max_length=255)
    location = models.CharField(max_length=255, blank=True)
    start_date = models.DateField()
    end_date = models.DateField()
    notes = models.TextField(blank=True)
    status = models.CharField(max_length=20, choices=[
        ("pending", "Pending"),
        ("approved", "Approved"),
        ("rejected", "Rejected"),
    ], default="pending")
    reviewed_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="+"
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_business_trips"
        ordering = ["-start_date"]
        indexes = [models.Index(fields=["employee", "status"])]

    def __str__(self):
        return f"{self.title} - {self.employee}"


class Communication(models.Model):
    """Internal HR communications."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.ForeignKey("companies.Organization", on_delete=models.CASCADE, related_name="communications")
    title = models.CharField(max_length=255)
    content = models.TextField()
    communication_type = models.CharField(max_length=20, choices=[
        ("announcement", "Announcement"),
        ("memo", "Memo"),
        ("meeting", "Meeting"),
        ("policy", "Policy"),
    ])
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="hr_communications")
    is_pinned = models.BooleanField(default=False)
    attachments = models.JSONField(default=list)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "hr_communications"
        ordering = ["-is_pinned", "-created_at"]


class Survey(models.Model):
    """HR surveys and feedback collection."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.ForeignKey("companies.Organization", on_delete=models.CASCADE, related_name="surveys")
    title = models.CharField(max_length=255)
    description = models.TextField()
    # List of {id, type: short_text|long_text|single_choice|rating, label, options?, required}
    questions = models.JSONField(default=list)
    status = models.CharField(max_length=20, choices=[
        ("draft", "Draft"),
        ("active", "Active"),
        ("closed", "Closed"),
    ], default="draft")
    # Optional fill window. Survey accepts responses only between these (when set).
    open_at = models.DateTimeField(null=True, blank=True)
    close_at = models.DateTimeField(null=True, blank=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="hr_surveys")
    responses = models.JSONField(default=list)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_surveys"
        ordering = ["-created_at"]

    def is_open(self) -> bool:
        """True if the survey currently accepts responses (active + within window)."""
        if self.status != "active":
            return False
        now = timezone.now()
        if self.open_at and now < self.open_at:
            return False
        if self.close_at and now > self.close_at:
            return False
        return True


class SurveyResponse(models.Model):
    """A single respondent's submission to a Survey."""
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    survey = models.ForeignKey(Survey, on_delete=models.CASCADE, related_name="response_set")
    respondent = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="survey_responses")
    # List of {question_id, value}
    answers = models.JSONField(default=list)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "hr_survey_responses"
        ordering = ["-created_at"]
        constraints = [
            models.UniqueConstraint(fields=["survey", "respondent"], name="uniq_survey_respondent"),
        ]

    def __str__(self):
        return f"Response to {self.survey.title} by {self.respondent}"


class Family(models.Model):
    """Employee family members."""
    RELATIONSHIP_CHOICES = [
        ("spouse", "Spouse"),
        ("child", "Child"),
        ("parent", "Parent"),
        ("sibling", "Sibling"),
        ("other", "Other"),
    ]
    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="family_members")
    name = models.CharField(max_length=100)
    relationship = models.CharField(max_length=20, choices=RELATIONSHIP_CHOICES)
    date_of_birth = models.DateField(null=True, blank=True)
    phone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    occupation = models.CharField(max_length=100, blank=True)
    notes = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_families"
        ordering = ["name"]

    def __str__(self):
        return f"{self.name} ({self.relationship}) - {self.employee}"


DEFAULT_ATTENDANCE_SETTINGS = {
    "flexible_hours": {"core_start": "10:00", "core_end": "15:00", "flex_band_hours": 2},
    "tolerance": {
        "clock_in_grace_minutes": 10,
        "clock_out_grace_minutes": 10,
        "early_allowed_minutes": 30,
    },
    "late_rules": {
        "notify_after_minutes": 15,
        "penalty_after_minutes": 30,
        "monthly_allowed_count": 3,
    },
    "overtime": {
        "enabled": True,
        "trigger_after_minutes": 30,
        "weekday_rate": 1.5,
        "weekend_rate": 2,
        "holiday_rate": 3,
    },
    "breaks": {"duration_minutes": 60, "per_shift_count": 1, "auto_deduct": True},
    "geofencing": {"enabled": True, "radius_meters": 100, "enforce": True},
    "gps": {"enabled": True, "accuracy_meters": 50},
    "face": {"enabled": True, "min_confidence": 80, "fallback_selfie": True},
    "auto_checkout": {
        "enabled": False,
        "time": "23:59",
        "note": "Auto-closes open attendance at end of day.",
    },
}


class AttendanceSettings(models.Model):
    """One row per organization, holding its attendance config."""

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.OneToOneField("companies.Organization", on_delete=models.CASCADE, related_name="attendance_settings")
    settings = models.JSONField(default=dict, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_attendance_settings"
        verbose_name = "Attendance settings"
        verbose_name_plural = "Attendance settings"

    def __str__(self):
        return "Attendance settings"

    @classmethod
    def get_solo(cls, organization) -> "AttendanceSettings":
        obj, created = cls.objects.get_or_create(organization=organization, defaults={"settings": DEFAULT_ATTENDANCE_SETTINGS})
        if not created and not obj.settings:
            obj.settings = DEFAULT_ATTENDANCE_SETTINGS
            obj.save(update_fields=["settings", "updated_at"])
        return obj


DEFAULT_PAYROLL_SETTINGS = {
    "officer": {
        "name": "",
        "title": "Payroll Officer",
        "employee_id": "",
    },
    "period": {
        "type": "monthly",
        "cutoff_day": 25,
        "pay_day": 1,
        "pay_day_next_month": True,
    },
    "components": {
        "allowances": [
            {"key": "transport", "label": "Transport Allowance", "default_amount": 0},
            {"key": "meal", "label": "Meal Allowance", "default_amount": 0},
        ],
        "deductions": [
            {"key": "bpjs_kesehatan", "label": "BPJS Kesehatan", "rate": 0.01},
            {"key": "bpjs_jht", "label": "BPJS JHT", "rate": 0.02},
        ],
    },
    "tax": {
        "method": "netto",
        "ptkp_basis": "TK0",
    },
    "bpjs": {
        "kesehatan_employee_rate": 1.0,
        "kesehatan_employer_rate": 4.0,
        "jht_employee_rate": 2.0,
        "jht_employer_rate": 3.7,
        "jp_employee_rate": 1.0,
        "jp_employer_rate": 2.0,
        "jkk_rate": 0.24,
        "jkm_rate": 0.3,
    },
    "thr": {
        "enabled": True,
        "basis": "monthly_salary",
        "prorate_threshold_months": 12,
    },
    "currency": "IDR",
}


class PayrollSettings(models.Model):
    """One row per organization, holding its payroll config."""

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.OneToOneField("companies.Organization", on_delete=models.CASCADE, related_name="payroll_settings")
    settings = models.JSONField(default=dict, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_payroll_settings"
        verbose_name = "Payroll settings"
        verbose_name_plural = "Payroll settings"

    def __str__(self):
        return "Payroll settings"

    @classmethod
    def get_solo(cls, organization) -> "PayrollSettings":
        obj, created = cls.objects.get_or_create(organization=organization, defaults={"settings": DEFAULT_PAYROLL_SETTINGS})
        if not created and not obj.settings:
            obj.settings = DEFAULT_PAYROLL_SETTINGS
            obj.save(update_fields=["settings", "updated_at"])
        return obj


def _leave_type(key, label, *, quota=0, paid=True, **overrides):
    """One configured leave type, with the shape every type shares.

    `key` is what LeaveRequest.leave_type stores, so it is the stable
    identifier: renaming a label is cosmetic, but changing a key orphans every
    request already filed under the old one.
    """
    return {
        "key": key,
        "label": label,
        "enabled": True,
        "paid": paid,
        # 0 = no annual entitlement cap. Unpaid and event-driven leave (marriage,
        # bereavement) are usually granted per occurrence rather than accrued.
        "quota_days": quota,
        "accrual": {"mode": "annual", "monthly_days": 0},
        "carry_forward": {"enabled": False, "max_days": 0, "expires_month": 3},
        "rules": {
            "min_notice_days": 0,
            "max_consecutive_days": 0,
            "requires_document": False,
            "document_after_days": 0,
            "allow_half_day": True,
        },
        "approval": {"approvers": ["manager"], "auto_approve_under_days": 0},
        **overrides,
    }


# Indonesian statutory baselines (UU Ketenagakerjaan No. 13/2003, art. 79 & 93):
# 12 days annual leave after 12 months' service; marriage 3 days; death of an
# immediate family member 2 days. These are defaults an org may raise, not
# limits — nothing in this codebase enforces the statutory floor.
DEFAULT_LEAVE_SETTINGS = {
    "types": [
        _leave_type(
            "annual", "Annual Leave", quota=12,
            accrual={"mode": "monthly", "monthly_days": 1},
            carry_forward={"enabled": True, "max_days": 6, "expires_month": 3},
            rules={
                "min_notice_days": 3, "max_consecutive_days": 0,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": True,
            },
        ),
        _leave_type(
            "sick", "Sick Leave", quota=0,
            rules={
                "min_notice_days": 0, "max_consecutive_days": 0,
                # A doctor's note is the usual bar past a couple of days;
                # demanding one for a single day tends to push people to work sick.
                "requires_document": True, "document_after_days": 2,
                "allow_half_day": True,
            },
        ),
        _leave_type(
            "unpaid", "Unpaid Leave", quota=0, paid=False,
            rules={
                "min_notice_days": 7, "max_consecutive_days": 0,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": False,
            },
            approval={"approvers": ["manager", "hr"], "auto_approve_under_days": 0},
        ),
        _leave_type(
            "marriage", "Marriage Leave", quota=3,
            rules={
                "min_notice_days": 14, "max_consecutive_days": 3,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": False,
            },
        ),
        _leave_type(
            "bereavement", "Bereavement Leave", quota=2,
            rules={
                "min_notice_days": 0, "max_consecutive_days": 2,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": False,
            },
            approval={"approvers": ["manager"], "auto_approve_under_days": 2},
        ),
        _leave_type(
            "compassionate", "Compassionate Leave", quota=2,
            rules={
                "min_notice_days": 0, "max_consecutive_days": 2,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": True,
            },
        ),
        _leave_type(
            "emergency", "Emergency Leave", quota=2,
            rules={
                "min_notice_days": 0, "max_consecutive_days": 2,
                "requires_document": False, "document_after_days": 0,
                "allow_half_day": True,
            },
            approval={"approvers": ["manager"], "auto_approve_under_days": 1},
        ),
        # Pre-existing LeaveRequest choices. Kept so historical requests still
        # resolve to a configured type — dropping them would leave those rows
        # pointing at a key no longer in settings.
        _leave_type("maternity", "Maternity Leave", quota=90),
        _leave_type("paternity", "Paternity Leave", quota=2),
    ],
    "general": {
        # Leave year boundary. 1 = January, so the default is the calendar year.
        "year_start_month": 1,
        "allow_negative_balance": False,
        "count_public_holidays": False,
        # "Sandwich leave": days between two leave blocks counted as leave.
        "sandwich_leave": False,
    },
}


class LeaveSettings(models.Model):
    """One row per organization, holding its leave types and policy config."""

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.OneToOneField("companies.Organization", on_delete=models.CASCADE, related_name="leave_settings")
    settings = models.JSONField(default=dict, blank=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_leave_settings"
        verbose_name = "Leave settings"
        verbose_name_plural = "Leave settings"

    def __str__(self):
        return "Leave settings"

    @classmethod
    def get_solo(cls, organization) -> "LeaveSettings":
        obj, created = cls.objects.get_or_create(organization=organization, defaults={"settings": DEFAULT_LEAVE_SETTINGS})
        if not created and not obj.settings:
            obj.settings = DEFAULT_LEAVE_SETTINGS
            obj.save(update_fields=["settings", "updated_at"])
        return obj

    @classmethod
    def type_keys(cls, organization) -> list[str]:
        """Keys of this org's enabled leave types, for validating a request."""
        types = (cls.get_solo(organization).settings or {}).get("types", [])
        return [t["key"] for t in types if t.get("enabled", True) and t.get("key")]


class ShiftTemplate(models.Model):
    """Reusable working time pattern with optional flex hours + employee members."""

    id = models.UUIDField(primary_key=True, default=uuid7, editable=False)
    organization = models.ForeignKey("companies.Organization", on_delete=models.CASCADE, related_name="shift_templates")
    name = models.CharField(max_length=100)
    start_time = models.CharField(max_length=5, default="09:00", help_text="HH:MM")
    end_time = models.CharField(max_length=5, default="18:00", help_text="HH:MM")
    working_days = models.CharField(max_length=64, default="Mon-Fri")
    flex_enabled = models.BooleanField(default=False)
    members = models.ManyToManyField(Employee, blank=True, related_name="shift_templates")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "hr_shift_templates"
        ordering = ["name"]
        verbose_name = "Shift template"
        verbose_name_plural = "Shift templates"

    def __str__(self):
        return self.name