from django.db import models
from django.contrib.auth.models import User

# Create your models here.
# User.add_to_class('profiles',  models.FileField(upload_to='uploads/', null=True, blank=True, default="../static/dummy.png"))

import json
import numpy as np


class Profiles(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='user_profiles')

    name = models.CharField(max_length=255, null=True)
    phone = models.CharField(max_length=255, null=True)
    email = models.CharField(max_length=255, null=True)
    address = models.CharField(max_length=255, null=True)
    servicename = models.CharField(max_length=255, null=True)
    
    gender = models.CharField(max_length=255, null=True)
    profiles = models.FileField(upload_to='uploads/', null=True, blank=True, default="../static/dummy.png")

    password = models.CharField(max_length=255, null=True)
    gstnumber = models.CharField(max_length=255, null=True)
    pincode = models.CharField(max_length=255, null=True)


    doj = models.DateField(null=True)
    
    gst = models.FileField(upload_to='uploads/', null=True, blank=True)
    companylogo = models.FileField(upload_to='uploads/', null=True, blank=True)
    alteremail = models.CharField(max_length=255, null=True)
    alteremail1 = models.CharField(max_length=255, null=True)
    alteremail2 = models.CharField(max_length=255, null=True)


    pan = models.FileField(upload_to='uploads/', null=True, blank=True)
    role = models.SmallIntegerField(default=0)



    status = models.SmallIntegerField(default=1)
    geostatus = models.SmallIntegerField(default=1)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'profile'





class Department(models.Model):
    name = models.CharField(max_length=255, null=True)
    description = models.TextField(null=True)
    status = models.SmallIntegerField(default=1)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='deartment_created_by')
    updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='department_updated_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'department'
        ordering = ['pk']







class Designation(models.Model):
    department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True, blank=True)
    name = models.CharField(max_length=255, null=True)
    description = models.TextField(null=True)
    status = models.SmallIntegerField(default=1)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='designation_created_by')
    updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='designation_updated_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'designation'
        ordering = ['pk']

class Employee(models.Model):
    # Basic Info
    users = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='employees_profiles')

    name = models.CharField(max_length=255)
    mobile = models.CharField(max_length=15, unique=True)
    age = models.IntegerField(null=True, blank=True)
    address = models.TextField(null=True, blank=True)
    
    # Profile Picture (Image upload)
    photo = models.FileField(upload_to='uploads/', null=True, blank=True, default="../static/dummy.png")
    face_encoding_data = models.TextField(null=True, blank=True)
    pan = models.TextField(null=True, blank=True)


    ctc = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    grossSalary = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    basic = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    hra = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    conveyance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    otherAllowance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    
    # --- Deductions & Limits ---
    fixedPT = models.DecimalField(max_digits=10, decimal_places=2, default=208.00)
    pfLimit = models.DecimalField(max_digits=10, decimal_places=2, default=1800.00)
    
    # --- Monthly Variables (இவை ஒவ்வொரு மாதமும் மாறக்கூடியவை) ---
    advance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    ot = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    reimbursement = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    incentive = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)

    
    # Foreign Keys (Linking to your existing models)
    department = models.ForeignKey(
        'Department', 
        on_delete=models.SET_NULL, 
        null=True, 
        related_name='employees'
    )
    designation = models.ForeignKey(
        'Designation', 
        on_delete=models.SET_NULL, 
        null=True, 
        related_name='employees'
    )
    
    # Metadata
    doj = models.DateField(verbose_name="Date of Joining")
    status = models.SmallIntegerField(default=1) # 1: Active, 0: Resigned/Inactive
    
    # Audit Fields
    created_by = models.ForeignKey(
        User, on_delete=models.CASCADE, null=True, blank=True,
        related_name='employee_created_by'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


    def save_encoding(self, encoding_array):
        self.face_encoding_data = json.dumps(encoding_array.tolist())
        self.save()

    # சேமிக்கப்பட்ட டேட்டாவை மீண்டும் Array-ஆக எடுக்க
    def get_encoding(self):
        if self.face_encoding_data:
            return np.array(json.loads(self.face_encoding_data))
        return None

    class Meta:
        db_table = 'employees'
        ordering = ['-created_at']


class Attendance(models.Model):
    # எந்த மாணவர் என்பதைக் குறிக்க (ForeignKey)
    employee = models.ForeignKey('Employee', on_delete=models.CASCADE, 
        null=True, 
        related_name='attendance_records'
    )
    
    # எந்த கோச் அல்லது அட்மின் அட்டெண்டன்ஸ் எடுக்கிறார்
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)

    # Attendance தேதி (தானாக இன்றைய தேதி விழும்)
    date = models.DateField(auto_now_add=False) 

    # வருகைப் பதிவு நிலை (Present / Absent)
    ATTENDANCE_STATUS = (
        ('Present', 'Present'),
        ('Absent', 'Absent'),
    )
    in_time = models.TimeField(null=True, blank=True)  # உள்ளே வந்த நேரம்
    out_time = models.TimeField(null=True, blank=True) # வெளியே சென்ற நேரம்
    status = models.CharField(max_length=10, choices=ATTENDANCE_STATUS)

    location = models.TextField(null=True)


    

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'attendance'





class Announcement(models.Model):
    description = models.TextField(null=True)
    date = models.DateField(null=True, blank=True)
    status = models.SmallIntegerField(default=1)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='announcements_created_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'announcement'



class LeaveRequest(models.Model):
    type = models.CharField(max_length=255, null=True)
    reason = models.TextField(null=True)
    total_days = models.CharField(max_length=255, null=True)
    leave_dates = models.JSONField(blank=True, null=True)
    why = models.TextField(null=True)


    status = models.SmallIntegerField(default=1)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='leaverequest_created_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'leaverequest'

 


class LeaveRequests(models.Model):
    LEAVE_TYPES = [
        ("Casual", "Casual Leave"),
        ("Sick", "Sick Leave"),
        ("Earned", "Earned Leave"),
        ("Unpaid", "Unpaid Leave"),
    ]
    STATUS_CHOICES = [
        ("Pending", "Pending"),
        ("Approved", "Approved"),
        ("Rejected", "Rejected"),
    ]

    employee = models.ForeignKey(
        "Employee", on_delete=models.CASCADE, related_name="leave_requests"
    )
    leave_type = models.CharField(max_length=20, choices=LEAVE_TYPES, default="Casual")
    start_date = models.DateField()
    end_date = models.DateField()
    description = models.TextField(blank=True, null=True)

    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="Pending")
    admin_note = models.CharField(max_length=255, blank=True, null=True)

    applied_at = models.DateTimeField(auto_now_add=True)
    reviewed_by = models.ForeignKey(
        User, null=True, blank=True, on_delete=models.SET_NULL, related_name="+"
    )
    reviewed_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-applied_at"]
        # db_table வேணாம் — Django default-ஆ "myapp_leaverequest" create பண்ணும்,
        # பழைய "leaverequest" table-ஐ தொடாது.

    @property
    def days_count(self):
        return (self.end_date - self.start_date).days + 1

    def __str__(self):
        return f"{self.employee} · {self.leave_type} · {self.start_date}"

class Setting(models.Model):
    """
    Company-wide settings — ONE row mattum irukkum (pk=1).
    App-la irundhu Shift, Notification, Company, Security settings
    ellam indha ஒரே row-la thaan save aagum (singleton pattern).
    """

    # ── Shift & Attendance ──
    shift_start = models.TimeField(null=True, blank=True, default="09:30")
    shift_end = models.TimeField(null=True, blank=True, default="18:30")
    weekly_off = models.CharField(max_length=50, default="Sun")   # "Sun,Sat" maadhiri comma separated
    grace_minutes = models.PositiveIntegerField(default=10)       # late mark grace period
    half_day_hours = models.DecimalField(max_digits=4, decimal_places=1, default=4.0)
    overtime_enabled = models.BooleanField(default=True)
    geofence_enabled = models.BooleanField(default=False)
    face_sensitivity = models.CharField(
        max_length=10,
        choices=(("Low", "Low"), ("Medium", "Medium"), ("High", "High")),
        default="Medium",
    )

    # ── Notifications ──
    push_notifications = models.BooleanField(default=True)
    email_alerts = models.BooleanField(default=True)
    absent_alerts = models.BooleanField(default=True)
    leave_alerts = models.BooleanField(default=True)

    # ── Company Info ──
    company_name = models.CharField(max_length=255, default="Manju Associates")
    time_zone = models.CharField(max_length=100, default="Asia/Kolkata (GMT +5:30)")

    # ── Security ──
    biometric_login = models.BooleanField(default=False)
    two_factor = models.BooleanField(default=False)

    # ── App Preferences ──
    dark_mode = models.BooleanField(default=False)
    auto_backup = models.BooleanField(default=True)
    date_format = models.CharField(max_length=20, default="DD/MM/YYYY")

    updated_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="settings_updated_by"
    )
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = "settings"

    def __str__(self):
        return self.company_name


class Holiday(models.Model):
    """Company holiday list — Add Holiday screen-kku."""
    name = models.CharField(max_length=255)
    date = models.DateField()
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="holiday_created_by"
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "holiday"
        ordering = ["date"]

    def __str__(self):
        return f"{self.name} ({self.date})"






class Otp(models.Model):
    email = models.CharField(max_length=255, null=True)
    status = models.SmallIntegerField(default=1)
    otp = models.CharField(max_length=6, blank=True)
    otp_created_at = models.DateTimeField(blank=True, null=True)

    class Meta:
        db_table = 'otp'
        ordering = ['pk']



class Payslip(models.Model):

    employee = models.ForeignKey('Employee', on_delete=models.CASCADE, 
        null=True, 
        related_name='payslip_records'
    )
    empname = models.CharField(max_length=255, null=True)
    empid = models.CharField(max_length=255, null=True)
    emppan = models.CharField(max_length=255, null=True)
    ctc = models.CharField(max_length=255, null=True)
    grosssalary = models.CharField(max_length=255, null=True)
    perdaysalary = models.CharField(max_length=255, null=True)
    basicsalary = models.CharField(max_length=255, null=True)
    hra = models.CharField(max_length=255, null=True)
    conveyance = models.CharField(max_length=255, null=True)
    otherallowance = models.CharField(max_length=255, null=True)
    totaldays = models.CharField(max_length=255, null=True)
    workingdays = models.CharField(max_length=255, null=True)
    baiscnew = models.CharField(max_length=255, null=True)
    hranew = models.CharField(max_length=255, null=True)
    conveyancenew = models.CharField(max_length=255, null=True)
    otherallowancenew = models.CharField(max_length=255, null=True)
    grosspay = models.CharField(max_length=255, null=True)
    variablepfgross = models.CharField(max_length=255, null=True)
    esiless = models.CharField(max_length=255, null=True)
    pfless = models.CharField(max_length=255, null=True)
    bank = models.CharField(max_length=255, null=True)
    uan = models.CharField(max_length=255, null=True)
    lesspt = models.CharField(max_length=255, null=True)
    pf = models.CharField(max_length=255, null=True)
    esi = models.CharField(max_length=255, null=True)
    
    advance = models.CharField(max_length=255, null=True)
    netpay = models.CharField(max_length=255, null=True)
    ot = models.CharField(max_length=255, null=True)
    reimbursement = models.CharField(max_length=255, null=True)
    incentive = models.CharField(max_length=255, null=True)
    topay = models.CharField(max_length=255, null=True)
    month = models.CharField(max_length=255, null=True)
    status = models.SmallIntegerField(default=1)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'payslip'



class Product(models.Model):
    company_name = models.CharField(max_length=255, null=True)
    project_name = models.CharField(max_length=255, null=True)
    application = models.CharField(max_length=255, null=True)
    country = models.CharField(max_length=255, null=True)
    deliverydate = models.CharField(max_length=255, null=True)
    bidclosedate = models.CharField(max_length=255, null=True)

    s2dailyflow = models.CharField(max_length=255, null=True)
    s2monthflow = models.CharField(max_length=255, null=True)
    s2weekflow = models.CharField(max_length=255, null=True)
    s2maxdailyflow = models.CharField(max_length=255, null=True)
    s2hourflow = models.CharField(max_length=255, null=True)
    s2bod = models.CharField(max_length=255, null=True)
    s2bodout = models.CharField(max_length=255, null=True)
    s2bodrate = models.CharField(max_length=255, null=True)
    s2cod = models.CharField(max_length=255, null=True)
    s2codout = models.CharField(max_length=255, null=True)
    s2codrate = models.CharField(max_length=255, null=True)
    s2tss = models.CharField(max_length=255, null=True)
    s2tssout = models.CharField(max_length=255, null=True)
    s2tssrate = models.CharField(max_length=255, null=True)
    s2tn = models.CharField(max_length=255, null=True)
    s2tnout = models.CharField(max_length=100, null=True)
    s2tnrate = models.CharField(max_length=100, null=True)
    s2tp = models.CharField(max_length=100, null=True)
    s2tpout = models.CharField(max_length=100, null=True)
    s2tprate = models.CharField(max_length=100, null=True)
    s2oil = models.CharField(max_length=100, null=True)
    s2oilout = models.CharField(max_length=100, null=True)
    s2oilrate = models.CharField(max_length=100, null=True)
    s2avgwatertemp = models.CharField(max_length=100, null=True)
    s2avgwatertempout = models.CharField(max_length=100, null=True)
    s2avgwatertemprate = models.CharField(max_length=100, null=True)
    s2maxwatertemp = models.CharField(max_length=100, null=True)
    s2maxwatertempout = models.CharField(max_length=100, null=True)
    s2maxwatertemprate = models.CharField(max_length=100, null=True)
    s2minwatertemp = models.CharField(max_length=100, null=True)
    s2minwatertempout = models.CharField(max_length=100, null=True)
    s2minwatertemprate = models.CharField(max_length=100, null=True)

    Davgflux1 = models.CharField(max_length=100, null=True)
    Davgflux2 = models.CharField(max_length=100, null=True)
    Dpeakflux = models.CharField(max_length=100, null=True)
    Msurfacearea1 = models.CharField(max_length=100, null=True)
    Msurfacearea2 = models.CharField(max_length=100, null=True)
    productcode = models.CharField(max_length=100, null=True)
    Noofmodule = models.CharField(max_length=100, null=True)
    Noofmtrain = models.CharField(max_length=100, null=True)
    Noofmptrain = models.CharField(max_length=100, null=True)
    TNM = models.CharField(max_length=100, null=True)
    TMsurfacearea = models.CharField(max_length=100, null=True)
    Aavgflux1 = models.CharField(max_length=100, null=True)
    Aavgflux2 = models.CharField(max_length=100, null=True)
    Apeakflux1 = models.CharField(max_length=100, null=True)
    Apeakflux2 = models.CharField(max_length=100, null=True)

    s4AT1 = models.CharField(max_length=100, null=True)
    s4AT2 = models.CharField(max_length=100, null=True)
    s4AT3 = models.CharField(max_length=100, null=True)
    s4AT4 = models.CharField(max_length=100, null=True)
    s4AT5 = models.CharField(max_length=100, null=True)
    s4AT6 = models.CharField(max_length=100, null=True)
    s4AT7 = models.CharField(max_length=100, null=True)
    s4TT1 = models.CharField(max_length=100, null=True)
    s4TT2 = models.CharField(max_length=100, null=True)
    s4TT3 = models.CharField(max_length=100, null=True)
    s4TT4 = models.CharField(max_length=100, null=True)
    s4TT5 = models.CharField(max_length=100, null=True)
    s4TT6 = models.CharField(max_length=100, null=True)
    s4TT7 = models.CharField(max_length=100, null=True)
    s4MT1 = models.CharField(max_length=100, null=True)
    s4MT2 = models.CharField(max_length=100, null=True)
    s4MT3 = models.CharField(max_length=100, null=True)
    s4MT4 = models.CharField(max_length=100, null=True)
    s4MT5 = models.CharField(max_length=100, null=True)
    s4MT6 = models.CharField(max_length=100, null=True)
    s4MT7 = models.CharField(max_length=100, null=True)

    





    
    status = models.SmallIntegerField(default=1)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='product_created_by')
    updated_by = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,
                                   related_name='product_updated_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'product'
        ordering = ['pk']



class Notification(models.Model):
    
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='Notifications')
    type = models.CharField(max_length=20)
    message = models.TextField()
    status = models.SmallIntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'notification'
        ordering = ['pk']

"""
══════════════════════════════════════════════
 FEES MANAGEMENT — models.py addition
 Manju Associates — GST / IT Filing fee tracking

 Idhu unga existing models.py file-oda SAME app-la paste pannunga
 (Employee model already irukura file-layE), athuvum 'from .models
 import Employee' venaam — same file-la irundha direct-a use pannunga.
 Vera app-la vecha 'from employees.models import Employee' nu
 maathikonga.
══════════════════════════════════════════════
"""
import calendar
from datetime import date

from django.db import models, transaction, IntegrityError
from django.contrib.auth.models import User
from django.utils import timezone

# ⚠️ Employee already vera file/app-la irundha, idha uncomment pannunga:
# from employees.models import Employee


BILLING_FREQUENCY_CHOICES = (
    ('Monthly', 'Monthly'),
    ('Quarterly', 'Quarterly'),
    ('Yearly', 'Yearly'),
    ('One-Time', 'One-Time'),
)

SERVICE_CHOICES = (
    ('GST Filing', 'GST Filing'),
    ('GST Renewal', 'GST Renewal'),
    ('IT Filing', 'IT Filing'),
    ('IT Filing Renewal', 'IT Filing Renewal'),
    ('TDS Filing', 'TDS Filing'),
    ('ROC Filing', 'ROC Filing'),
    ('Trademark', 'Trademark'),
    ('Insurance', 'Insurance'),
    ('Other', 'Other'),
)

PAYMENT_STATUS_CHOICES = (
    ('Paid', 'Paid'),
    ('Pending', 'Pending'),
    ('Partial', 'Partial'),
)

PAYMENT_MODE_CHOICES = (
    ('Cash', 'Cash'),
    ('UPI', 'UPI'),
    ('Bank Transfer', 'Bank Transfer'),
    ('Cheque', 'Cheque'),
    ('Other', 'Other'),
)


# ══════════════════════════════════════════════
#  CLIENT — GST / IT Filing client (Ganapathy Store, etc.)
# ══════════════════════════════════════════════
class FeeClient(models.Model):
    # ⭐ Auto-generated UNIQUE client ID — MAS1, MAS2, MAS3 ...
    # (client add pannumbodhu thaanaave create aagum, manual-a type panna theva illa)
    CODE_PREFIX = 'MAS'
    client_code = models.CharField(
        max_length=20, unique=True, null=True, blank=True, editable=False
    )

    name = models.CharField(max_length=255)
    mobile = models.CharField(max_length=15, null=True, blank=True)
    address = models.TextField(null=True, blank=True)
    gst_number = models.CharField(max_length=20, null=True, blank=True)

    status = models.SmallIntegerField(default=1)  # 1: Active, 0: Inactive

    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='fee_clients_created_by'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'fee_clients'
        ordering = ['name']

    def __str__(self):
        return f"{self.client_code or '-'} · {self.name}"

    @classmethod
    def next_client_code(cls):
        """Ippo irukura highest number-ku +1 (MAS7 irundha -> MAS8)."""
        numbers = []
        for code in cls.objects.filter(
            client_code__startswith=cls.CODE_PREFIX
        ).values_list('client_code', flat=True):
            suffix = code[len(cls.CODE_PREFIX):]
            if suffix.isdigit():
                numbers.append(int(suffix))
        return f"{cls.CODE_PREFIX}{(max(numbers) + 1) if numbers else 1}"

    def save(self, *args, **kwargs):
        # client_code illana auto-generate. Rendu peru ore nerathula add pannaalum
        # (unique constraint fail aagi) andha attempt-ah marubadiyum try pannum.
        if self.client_code:
            return super().save(*args, **kwargs)
        for attempt in range(5):
            self.client_code = self.next_client_code()
            try:
                with transaction.atomic():
                    return super().save(*args, **kwargs)
            except IntegrityError:
                if attempt == 4:
                    raise


# ══════════════════════════════════════════════
#  FEE — one billing record per client per service/cycle.
#  paid_amount / balance / payment_status ella automatic-a
#  recalculate aagum (FeePayment history vachu) — manual-a
#  thattitu set panna theva illa.
# ══════════════════════════════════════════════
class Fee(models.Model):
    client = models.ForeignKey(
        FeeClient, on_delete=models.CASCADE, related_name='fees'
    )
    service = models.CharField(max_length=50, choices=SERVICE_CHOICES, default='GST Filing')
    service_other = models.CharField(max_length=100, null=True, blank=True)  # service="Other" na idhula custom name

    fee_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    billing_frequency = models.CharField(
        max_length=20, choices=BILLING_FREQUENCY_CHOICES, default='Monthly'
    )

    due_date = models.DateField()
    next_due_date = models.DateField(null=True, blank=True)

    paid_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    balance = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    payment_status = models.CharField(
        max_length=10, choices=PAYMENT_STATUS_CHOICES, default='Pending'
    )

    # ⭐ Idha yaaru fee record create pannaanganu track pannum (admin/employee)
    created_by = models.ForeignKey(
        'Employee', on_delete=models.SET_NULL, null=True, blank=True,
        related_name='fees_created_by'
    )
    notes = models.TextField(null=True, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'fees'
        ordering = ['-due_date']

    def __str__(self):
        return f"{self.client.name} — {self.service} — ₹{self.fee_amount}"

    # ── auto recalc, ovvoru payment add/edit/delete aanadhum call aagum ──
    def recalc(self):
        # ✅ fee_amount string-a irundhalум (e.g. "6000" JSON-lerundhu வந்தா),
        # Decimal-a explicit-a convert pannாthா "str - Decimal" TypeError varum.
        from decimal import Decimal
        total_paid = self.payments.aggregate(total=models.Sum('amount'))['total'] or Decimal('0')
        fee_amount = Decimal(str(self.fee_amount))

        self.paid_amount = total_paid
        self.balance = fee_amount - total_paid

        if total_paid <= 0:
            self.payment_status = 'Pending'
        elif total_paid < fee_amount:
            self.payment_status = 'Partial'
        else:
            self.payment_status = 'Paid'

        self.save(update_fields=['paid_amount', 'balance', 'payment_status', 'updated_at'])

    # ── billing frequency vachu next cycle due date calculate pannum ──
    def compute_next_due_date(self):
        d = self.due_date
        if self.billing_frequency == 'Monthly':
            months_to_add = 1
        elif self.billing_frequency == 'Quarterly':
            months_to_add = 3
        elif self.billing_frequency == 'Yearly':
            months_to_add = 12
        else:
            return None

        month_index = d.month - 1 + months_to_add
        year = d.year + month_index // 12
        month = month_index % 12 + 1
        day = min(d.day, calendar.monthrange(year, month)[1])
        return date(year, month, day)

    def save(self, *args, **kwargs):
        if not self.next_due_date:
            # first save-la due_date vachu next cycle pre-fill pannidalam
            self.next_due_date = self.compute_next_due_date()
        super().save(*args, **kwargs)


# ══════════════════════════════════════════════
#  FEE PAYMENT — payment history. Ovvoru "collection" entry-um
#  idhu, employee FK kattaayama irukum — "intha month evlo
#  collection pannirukaanga" report idhu vachu thaan varum.
# ══════════════════════════════════════════════
class FeePayment(models.Model):
    fee = models.ForeignKey(Fee, on_delete=models.CASCADE, related_name='payments')

    # ⭐⭐ MANDATORY — fees add pannra ella pag-layum idhu vaangunga.
    collected_by = models.ForeignKey(
        'Employee', on_delete=models.SET_NULL, null=True, blank=True,
        related_name='fee_payments_collected'
    )

    amount = models.DecimalField(max_digits=10, decimal_places=2)
    payment_date = models.DateField(default=timezone.now)
    payment_mode = models.CharField(max_length=20, choices=PAYMENT_MODE_CHOICES, default='Cash')
    reference_no = models.CharField(max_length=100, null=True, blank=True)  # UPI ref / cheque no
    note = models.CharField(max_length=255, null=True, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'fee_payments'
        ordering = ['-payment_date', '-created_at']

    def __str__(self):
        return f"{self.fee.client.name} — ₹{self.amount} on {self.payment_date}"

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.fee.recalc()  # ✅ save aana udane parent Fee balance/status refresh

    def delete(self, *args, **kwargs):
        fee = self.fee
        super().delete(*args, **kwargs)
        fee.recalc()


import os
import re
import uuid

from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator, MaxLengthValidator, RegexValidator
from django.db import models
from django.db.models.functions import Lower
from django.db.models.signals import post_delete
from django.dispatch import receiver

STATUS_ACTIVE = 1
STATUS_INACTIVE = 2
STATUS_CHOICES = [(STATUS_ACTIVE, "Active"), (STATUS_INACTIVE, "Inactive")]

CLIENT_CODE_VALIDATOR = RegexValidator(r"^[A-Z0-9][A-Z0-9/_-]{0,19}$", "Client ID can have letters, numbers, - _ / only (max 20).")
PHONE_VALIDATOR = RegexValidator(r"^[6-9]\d{9}$", "Enter a valid 10-digit mobile number.")
PAN_VALIDATOR = RegexValidator(r"^[A-Z]{5}[0-9]{4}[A-Z]$", "Enter a valid PAN (example: ABCDE1234F).")
AADHAAR_VALIDATOR = RegexValidator(r"^[2-9][0-9]{11}$", "Aadhaar must be 12 digits.")
GSTIN_VALIDATOR = RegexValidator(r"^\d{2}[A-Z]{5}\d{4}[A-Z][A-Z\d]Z[A-Z\d]$", "Enter a valid 15-character GSTIN.")
IFSC_VALIDATOR = RegexValidator(r"^[A-Z]{4}0[A-Z0-9]{6}$", "Enter a valid IFSC (example: HDFC0001234).")
ACCOUNT_VALIDATOR = RegexValidator(r"^\d{9,18}$", "Account number must be 9 to 18 digits.")
PINCODE_VALIDATOR = RegexValidator(r"^[1-9]\d{5}$", "Enter a valid 6-digit pincode.")


def only_digits(value):
    return re.sub(r"\D", "", value or "")


# ------------------------------------------------------------------ Service master
class Service(models.Model):
    name = models.CharField(max_length=150)
    status = models.SmallIntegerField(default=STATUS_ACTIVE, choices=STATUS_CHOICES)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='service_created_by')
    updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='service_updated_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'service'
        ordering = ['name']
        constraints = [
            # "gst" and "GST" can never both exist
            models.UniqueConstraint(Lower("name"), name="uniq_service_name_ci"),
        ]

    def __str__(self):
        return self.name


# ------------------------------------------------------------------ Client master
class Client(models.Model):
    # Type the existing client ID for old clients (C-101, MAS-00125 ...).
    # Leave blank -> system generates the next free one: MAS-00001, MAS-00002 ...
    client_code = models.CharField(max_length=20, unique=True, null=True, blank=True, validators=[CLIENT_CODE_VALIDATOR])

    name = models.CharField(max_length=200, db_index=True)
    business_name = models.CharField(max_length=200, blank=True, default="")
    address = models.TextField(blank=True, default="", validators=[MaxLengthValidator(500)])
    city = models.CharField(max_length=100, blank=True, default="")
    pincode = models.CharField(max_length=6, blank=True, default="", validators=[PINCODE_VALIDATOR])

    phone = models.CharField(max_length=10, db_index=True, validators=[PHONE_VALIDATOR])
    email = models.EmailField(blank=True, default="")

    pan = models.CharField(max_length=10, blank=True, default="", validators=[PAN_VALIDATOR])
    aadhaar = models.CharField(max_length=12, blank=True, default="", validators=[AADHAAR_VALIDATOR])
    gstin = models.CharField(max_length=15, blank=True, default="", validators=[GSTIN_VALIDATOR])

    bank_name = models.CharField(max_length=100, blank=True, default="")
    account_holder = models.CharField(max_length=150, blank=True, default="")
    account_number = models.CharField(max_length=18, blank=True, default="", validators=[ACCOUNT_VALIDATOR])
    ifsc = models.CharField(max_length=11, blank=True, default="", validators=[IFSC_VALIDATOR])

    # One client -> many services (renewal date is stored per service in ClientService)
    services = models.ManyToManyField(Service, through="ClientService", related_name="clients", blank=True)

    # File delivered to the client?  (toggle in the form; date is filled automatically)
    file_delivered = models.BooleanField(default=False, db_index=True)
    file_delivered_on = models.DateField(null=True, blank=True)

    notes = models.TextField(blank=True, default="", validators=[MaxLengthValidator(2000)])
    status = models.SmallIntegerField(default=STATUS_ACTIVE, choices=STATUS_CHOICES, db_index=True)

    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='client_created_by')
    updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='client_updated_by')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'client'
        ordering = ['-id']
        constraints = [
            # blank PAN / Aadhaar are allowed many times, filled ones must be unique
            models.UniqueConstraint(fields=["pan"], condition=~models.Q(pan=""), name="uniq_client_pan"),
            models.UniqueConstraint(fields=["aadhaar"], condition=~models.Q(aadhaar=""), name="uniq_client_aadhaar"),
        ]

    def __str__(self):
        return f"{self.client_code or 'NEW'} - {self.name}"

    def normalize(self):
        for f in ("name", "business_name", "address", "city", "email", "bank_name", "account_holder", "notes"):
            setattr(self, f, (getattr(self, f) or "").strip())
        self.client_code = (self.client_code or "").strip().upper() or None   # blank -> NULL
        self.email = self.email.lower()
        self.pan = (self.pan or "").strip().upper()
        self.gstin = (self.gstin or "").strip().upper()
        self.ifsc = (self.ifsc or "").strip().upper()
        self.aadhaar = only_digits(self.aadhaar)
        self.account_number = only_digits(self.account_number)
        self.pincode = only_digits(self.pincode)
        phone = only_digits(self.phone)
        if len(phone) == 12 and phone.startswith("91"):
            phone = phone[2:]
        self.phone = phone

    def validate_fields(self):
        """Clean the values, then run every field validator."""
        self.normalize()
        self.clean_fields()

    def save(self, *args, **kwargs):
        creating = self._state.adding
        self.normalize()
        super().save(*args, **kwargs)
        if creating and not self.client_code:
            n = self.pk
            while Client.objects.filter(client_code=f"MAS-{n:05d}").exists():   # skip IDs typed by hand
                n += 1
            self.client_code = f"MAS-{n:05d}"
            super().save(update_fields=["client_code"])


class ClientService(models.Model):
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="client_services")
    # PROTECT: a service that clients are using can't be deleted by mistake
    service = models.ForeignKey(Service, on_delete=models.PROTECT, related_name="client_services")
    renewal_date = models.DateField(null=True, blank=True, db_index=True)   # used by "Next Renewals"
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'client_service'
        constraints = [models.UniqueConstraint(fields=["client", "service"], name="uniq_client_service")]

    def __str__(self):
        return f"{self.client_id} -> {self.service_id}"


class RenewalHistory(models.Model):
    """One row every time a service is renewed ("Mark renewed" button). Never edited, only added."""
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="renewal_history")
    service = models.ForeignKey(Service, on_delete=models.PROTECT, related_name="renewal_history")
    old_date = models.DateField(null=True, blank=True)     # renewal date before this renewal
    new_date = models.DateField()                          # next renewal date after it
    renewed_on = models.DateField()                        # the day the renewal was actually done
    remarks = models.CharField(max_length=255, blank=True, default="")
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='renewal_created_by')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'renewal_history'
        ordering = ['-renewed_on', '-id']
        indexes = [models.Index(fields=['client', '-renewed_on'])]

    def __str__(self):
        return f"{self.client_id} {self.service_id} {self.renewed_on}"


# ------------------------------------------------------------------ Client documents (media folder)
def client_document_path(instance, filename):
    """MEDIA_ROOT/client_documents/<client id>/<random>.<ext>  (random name: URLs can't be guessed)."""
    ext = os.path.splitext(filename)[1].lower()
    return f"client_documents/{instance.client_id}/{uuid.uuid4().hex}{ext}"


DOC_TYPE_CHOICES = [
    ("pan", "PAN Card"), ("aadhaar", "Aadhaar Card"), ("bank", "Bank Document"), ("gst", "GST Certificate"),
    ("fssai", "FSSAI Certificate"), ("itr", "IT Filing"), ("other", "Other"),
]


class ClientDocument(models.Model):
    client = models.ForeignKey(Client, on_delete=models.CASCADE, related_name="documents")
    doc_type = models.CharField(max_length=20, choices=DOC_TYPE_CHOICES, default="other", db_index=True)
    file = models.FileField(upload_to=client_document_path, max_length=255,
                            validators=[FileExtensionValidator(["pdf", "jpg", "jpeg", "png"])])
    original_name = models.CharField(max_length=255, blank=True, default="")
    content_type = models.CharField(max_length=100, blank=True, default="")
    size = models.PositiveIntegerField(default=0)
    remarks = models.CharField(max_length=255, blank=True, default="")
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='clientdoc_created_by')
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'client_document'
        ordering = ['-created_at', '-id']

    def __str__(self):
        return f"{self.client_id} - {self.get_doc_type_display()}"


@receiver(post_delete, sender=ClientDocument)
def delete_file_from_media(sender, instance, **kwargs):
    """Remove the physical file when the record is deleted (also when the client is deleted)."""
    if instance.file:
        instance.file.delete(save=False)



 