from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
from agreements.utils.helper import generate_agreement_number, validate_timeline_overlap
import uuid
from decimal import Decimal
from django.contrib.auth.hashers import make_password, check_password
import secrets

# ---------- Agency (Company) ----------
class Agency(models.Model):
    company_name = models.CharField(max_length=255)
    address = models.TextField(blank=True, null=True)
    phone = models.CharField(max_length=20, blank=True, null=True)
    email = models.EmailField(blank=True, null=True)
    logo = models.ImageField(upload_to='agency/logos/', blank=True, null=True)
    gst = models.CharField(max_length=20, blank=True, null=True)
    pan = models.CharField(max_length=20, blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    signature = models.ImageField(upload_to='agency/signatures/', blank=True, null=True)

    def __str__(self):
        return self.company_name

# ---------- Client ----------
class Client(models.Model):
    company_name = models.CharField(max_length=255)
    contact_person = models.CharField(max_length=255)
    designation = models.CharField(max_length=255, blank=True, null=True)
    email = models.EmailField()
    phone = models.CharField(max_length=20, blank=True, null=True)
    address = models.TextField(blank=True, null=True)
    gst = models.CharField(max_length=20, blank=True, null=True)
    pan = models.CharField(max_length=20, blank=True, null=True)
    website = models.URLField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)

    def __str__(self):
        return f"{self.company_name} ({self.contact_person})"

# ---------- Employee ----------
class Employee(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE, related_name='employee_profile')
    designation = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return self.user.get_full_name()

# ---------- Agreement ----------
class Agreement(models.Model):
    # ---------- Change 3: Updated status choices ----------
    STATUS_CHOICES = (
        ('DRAFT', 'Draft'),
        ('SENT', 'Sent'),
        ('VIEWED', 'Viewed'),
        ('PENDING_SIGNATURE', 'Pending Signature'),
        ('SIGNED', 'Signed'),
        ('REJECTED', 'Rejected'),
        ('ARCHIVED', 'Archived'),
    )

    agreement_no = models.CharField(max_length=20, unique=True, editable=False)
    title = models.CharField(max_length=255)
    agreement_date = models.DateField()
    referred_by = models.ForeignKey(Employee, on_delete=models.SET_NULL, null=True, blank=True, related_name='referred_agreements')
    agency = models.ForeignKey(Agency, on_delete=models.PROTECT, related_name='agreements')
    principal = models.ForeignKey(Client, on_delete=models.PROTECT, related_name='agreements')
    purpose = models.TextField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT')

    client_access_password = models.CharField(
        max_length=255,
        blank=True
    )

    client_sign_token = models.CharField(
        max_length=64,
        blank=True,
        null=True,
        unique=False
    )

    client_signed = models.BooleanField(default=False)

    client_signed_at = models.DateTimeField(
        null=True,
        blank=True
    )

    client_viewed = models.BooleanField(default=False)

    # ---------- Change 2: New fields for audit trail ----------
    client_viewed_at = models.DateTimeField(
        null=True,
        blank=True
    )

    client_ip = models.GenericIPAddressField(
        null=True,
        blank=True
    )

    client_browser = models.CharField(
        max_length=300,
        blank=True
    )

    client_device = models.CharField(
        max_length=300,
        blank=True
    )
    # --------------------------------------------------------

    # Versioning
    version_number = models.PositiveIntegerField(default=1)
    is_latest = models.BooleanField(default=True)
    parent_agreement = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='versions')

    # Template used
    template_used = models.ForeignKey('AgreementTemplate', on_delete=models.SET_NULL, null=True, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ['-created_at']
        unique_together = [['agreement_no', 'version_number']]

    def save(self, *args, **kwargs):
        # ---------- Change 6: unchanged ----------
        if not self.agreement_no:
            self.agreement_no = generate_agreement_number()

        if not self.client_sign_token:
            self.client_sign_token = secrets.token_urlsafe(32)

        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.agreement_no} v{self.version_number} - {self.title}"

    @property
    def total_duration(self):
        max_end = self.timeline_phases.aggregate(models.Max('end_day'))['end_day__max']
        return max_end or 0

    # ---------- Change 7: Helper method ----------
    def mark_client_viewed(self, ip=None, browser=None, device=None):
        self.client_viewed = True
        self.client_viewed_at = timezone.now()
        self.client_ip = ip
        self.client_browser = browser
        self.client_device = device
        self.status = 'VIEWED'
        self.save()

    # ---------- Change 8: Helper method ----------
    def mark_signed(self):
        self.client_signed = True
        self.client_signed_at = timezone.now()
        self.status = 'SIGNED'
        self.save()

# ---------- Hierarchical Scope with Node Types ----------
class ScopeNode(models.Model):
    NODE_TYPES = (
        ('SECTION', 'Section'),
        ('SUBSECTION', 'Sub Section'),
        ('FEATURE', 'Feature'),
    )
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='scope_nodes')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    node_type = models.CharField(max_length=20, choices=NODE_TYPES, default='SECTION')
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']
        unique_together = [['agreement', 'parent', 'order']]

    def __str__(self):
        return f"{self.get_node_type_display()}: {self.name}"

# ---------- Hierarchical Responsibility with Node Types ----------
class ResponsibilityNode(models.Model):
    NODE_TYPES = (
        ('SECTION', 'Section'),
        ('SUBSECTION', 'Sub Section'),
        ('ITEM', 'Item'),
    )
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='responsibility_nodes')
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    node_type = models.CharField(max_length=20, choices=NODE_TYPES, default='SECTION')
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']
        unique_together = [['agreement', 'parent', 'order']]

    def __str__(self):
        return f"{self.get_node_type_display()}: {self.name}"

# ---------- Timeline Phase (auto‑calculate duration) ----------
class TimelinePhase(models.Model):
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='timeline_phases')
    phase_name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    start_day = models.PositiveIntegerField()
    end_day = models.PositiveIntegerField()
    order = models.PositiveIntegerField()
    color = models.CharField(max_length=7, default='#007bff')
    icon = models.CharField(max_length=50, blank=True, null=True)
    progress = models.PositiveIntegerField(default=0, validators=[MaxValueValidator(100)])
    is_milestone = models.BooleanField(default=False)

    class Meta:
        ordering = ['order']
        unique_together = [['agreement', 'order']]

    def clean(self):
        if self.start_day > self.end_day:
            raise ValidationError("Start day cannot be after end day.")

        qs = TimelinePhase.objects.filter(
            agreement=self.agreement
        ).exclude(pk=self.pk)

        for phase in qs:
            overlap = (
                self.start_day < phase.end_day
                and self.end_day > phase.start_day
            )

            if overlap:
                raise ValidationError(
                    "Timeline overlaps with another phase."
                )

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

    @property
    def duration_days(self):
        return self.end_day - self.start_day + 1

    def __str__(self):
        return f"{self.phase_name} (Days {self.start_day}-{self.end_day})"

# ---------- Payment Section with Stored Calculated Fields ----------
# ---------- Payment Section with Stored Calculated Fields ----------
class PaymentSection(models.Model):
    agreement = models.OneToOneField(
        Agreement,
        on_delete=models.CASCADE,
        related_name='payment'
    )

    currency = models.CharField(max_length=10, default='USD')

    subtotal = models.DecimalField(
        max_digits=12,
        decimal_places=2
    )

    gst_percent = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=0
    )

    discount_percent = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=0
    )

    tds_percent = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=0
    )

    advance_amount = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    # Stored calculated fields
    gst_amount = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    discount_amount = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    tds_amount = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    grand_total = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    final_payable = models.DecimalField(
        max_digits=12,
        decimal_places=2,
        default=0
    )

    payment_terms = models.TextField(
        blank=True,
        null=True
    )

    finalized = models.BooleanField(
        default=False
    )

    # ==========================================================
    # CALCULATE AMOUNTS
    # ==========================================================

    def calculate_amounts(self):
        """
        Calculate all payment amounts.

        This method ONLY calculates values.
        It does NOT save the object.
        """

        self.gst_amount = (
            self.gst_percent / Decimal("100")
        ) * self.subtotal

        self.discount_amount = (
            self.discount_percent / Decimal("100")
        ) * self.subtotal

        self.tds_amount = (
            self.tds_percent / Decimal("100")
        ) * self.subtotal

        self.grand_total = (
            self.subtotal
            + self.gst_amount
            - self.discount_amount
            - self.tds_amount
        )

        self.final_payable = (
            self.grand_total
            - self.advance_amount
        )

    # ==========================================================
    # FINALIZE PAYMENT
    # ==========================================================

    def calculate_and_save(self):
        """
        Recalculate and store all computed fields.

        This method is intended for an already existing
        PaymentSection object.
        """

        self.calculate_amounts()

        self.finalized = True

        # Normal save is safe here because this method
        # is called on an existing PaymentSection.
        super().save(
            update_fields=[
                'gst_amount',
                'discount_amount',
                'tds_amount',
                'grand_total',
                'final_payable',
                'finalized',
            ]
        )

    # ==========================================================
    # NORMAL SAVE
    # ==========================================================

    def save(self, *args, **kwargs):
        """
        Normal create/update operation.

        Calculate amounts before saving, but DO NOT call
        calculate_and_save(), otherwise it creates recursive saves.
        """

        if not self.finalized:
            self.calculate_amounts()

        super().save(*args, **kwargs)

    # ==========================================================

    def __str__(self):
        return f"Payment for {self.agreement.agreement_no}"

# ---------- Payment Milestones ----------
class PaymentMilestone(models.Model):
    payment = models.ForeignKey(PaymentSection, on_delete=models.CASCADE, related_name='milestones')
    milestone_name = models.CharField(max_length=255)
    percentage = models.DecimalField(max_digits=5, decimal_places=2, validators=[MinValueValidator(0), MaxValueValidator(100)])
    amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    due_date = models.DateField()
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']
        unique_together = [['payment', 'order']]

    def save(self, *args, **kwargs):
        if self.percentage and self.payment and not self.amount:
            self.amount = (self.percentage / 100) * self.payment.subtotal
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.milestone_name} ({self.percentage}%)"

# ---------- Annexure Template ----------
class AnnexureTemplate(models.Model):
    name = models.CharField(max_length=255)
    content = models.TextField()

    def __str__(self):
        return self.name

class Annexure(models.Model):
    agreement = models.OneToOneField(Agreement, on_delete=models.CASCADE, related_name='annexure')
    content = models.TextField()
    template = models.ForeignKey(AnnexureTemplate, on_delete=models.SET_NULL, null=True, blank=True)

    def __str__(self):
        return f"Annexure for {self.agreement.agreement_no}"

# ---------- Terms & Conditions ----------
class Terms(models.Model):
    name = models.CharField(max_length=255)
    content = models.TextField()

    def __str__(self):
        return self.name

class AgreementTerms(models.Model):
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='terms_links')
    terms = models.ForeignKey(Terms, on_delete=models.PROTECT)
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']
        unique_together = [['agreement', 'order']]

# ---------- Custom Dynamic Sections ----------
class CustomSection(models.Model):
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='custom_sections')
    section_type = models.CharField(max_length=100)
    name = models.CharField(max_length=255)
    content = models.TextField()
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']
        unique_together = [['agreement', 'order']]

# ---------- Agreement Attachments ----------
class AgreementAttachment(models.Model):
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='attachments')
    file = models.FileField(upload_to='agreement_attachments/')
    description = models.CharField(max_length=255, blank=True, null=True)
    uploaded_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.agreement.agreement_no} - {self.file.name}"

# ---------- Enhanced Signature with Metadata ----------
class Signature(models.Model):
    agreement = models.OneToOneField(Agreement, on_delete=models.CASCADE, related_name='signature')
    agency_name = models.CharField(max_length=255)
    agency_designation = models.CharField(max_length=255, blank=True, null=True)
    agency_signature = models.ImageField(upload_to='signatures/agency/', blank=True, null=True)
    client_name = models.CharField(max_length=255,blank=True, null=True)
    client_signature = models.ImageField(upload_to='signatures/client/', blank=True, null=True)
    signed_date = models.DateField(blank=True, null=True)
    # Metadata for electronic signing
    ip_address = models.GenericIPAddressField(blank=True, null=True)
    signed_time = models.DateTimeField(blank=True, null=True)
    device_info = models.CharField(max_length=255, blank=True, null=True)
    browser_info = models.CharField(max_length=255, blank=True, null=True)
    location = models.CharField(max_length=255, blank=True, null=True)

    # ---------- Change 4: Additional fields ----------
    signed_by_client = models.BooleanField(default=False)
    signed_by_agency = models.BooleanField(default=False)
    signature_hash = models.CharField(
        max_length=128,
        blank=True
    )
    # ------------------------------------------------

    def __str__(self):
        return f"Signature for {self.agreement.agreement_no}"

# ---------- Agreement History / Activity Log ----------
class AgreementHistory(models.Model):
    # ---------- Change 5: Extended action choices ----------
    ACTION_CHOICES = (
        ('CREATED', 'Created'),
        ('EDITED', 'Edited'),
        ('STATUS_CHANGED', 'Status Changed'),
        ('VIEWED', 'Viewed'),
        ('DOWNLOADED', 'Downloaded'),
        ('SIGNED', 'Signed'),
        ('APPROVED', 'Approved'),
        ('LOGIN', 'Client Login'),
        ('SIGN_REQUEST', 'Signature Requested'),
        ('SIGN_COMPLETED', 'Client Signed'),
        ('SIGN_REJECTED', 'Signing Rejected'),
    )
    # --------------------------------------------------
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='history')
    action = models.CharField(max_length=20, choices=ACTION_CHOICES)
    user = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True, blank=True)
    timestamp = models.DateTimeField(auto_now_add=True)
    details = models.JSONField(default=dict, blank=True)  # store extra info like old/new status

    def __str__(self):
        return f"{self.agreement.agreement_no} - {self.get_action_display()} at {self.timestamp}"

# ---------- Agreement Template (now stored relationally) ----------
class AgreementTemplate(models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

# Template sections (to replace JSON blob)
class TemplateSection(models.Model):
    template = models.ForeignKey(AgreementTemplate, on_delete=models.CASCADE, related_name='sections')
    section_type = models.CharField(max_length=50)  # 'scope', 'responsibility', 'timeline', 'payment', 'custom'
    data = models.JSONField(default=dict)  # stores the structure for that section
    order = models.PositiveIntegerField()

    class Meta:
        ordering = ['order']

# ---------- HTML/PDF Page Templates ----------
class AgreementPageTemplate(models.Model):
    name = models.CharField(max_length=200)

    template_pdf = models.FileField(
        upload_to="agreement_templates/"
    )

    top_margin = models.PositiveIntegerField(default=90)

    bottom_margin = models.PositiveIntegerField(default=70)

    left_margin = models.PositiveIntegerField(default=70)

    right_margin = models.PositiveIntegerField(default=70)

    is_default = models.BooleanField(default=False)

class AgreementPage(models.Model):
    agreement = models.ForeignKey(Agreement, on_delete=models.CASCADE, related_name='pages')
    page_template = models.ForeignKey(AgreementPageTemplate, on_delete=models.PROTECT)
    rendered_html = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Page for {self.agreement.agreement_no} using {self.page_template.name}"