from django.db import models
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.utils import timezone
from decimal import Decimal
import re

User = get_user_model()


def validate_positive_decimal(value):
    """
    Validator for positive decimal amounts.
    Requirements: 16.3
    """
    if value is not None and value < 0:
        raise ValidationError('Amount must be positive.')


def validate_two_decimal_places(value):
    """
    Validator for maximum 2 decimal places.
    Requirements: 16.3
    """
    if value is not None:
        decimal_places = abs(value.as_tuple().exponent)
        if decimal_places > 2:
            raise ValidationError('Amount must have no more than 2 decimal places.')


def validate_amount_field(value):
    """
    Combined validator for amounts: positive and max 2 decimal places.
    Requirements: 16.3
    """
    validate_positive_decimal(value)
    validate_two_decimal_places(value)


class Account(models.Model):
    """
    Account model representing individual accounts in the Chart of Accounts.
    """
    ACCOUNT_TYPES = [
        ('asset', 'Asset'),
        ('liability', 'Liability'),
        ('equity', 'Equity'),
        ('income', 'Income'),
        ('expense', 'Expense'),
    ]
    
    ASSET_SUBTYPES = [
        ('current_asset', 'Current Asset'),
        ('loan_portfolio', 'Loan Portfolio'),
        ('fixed_asset', 'Fixed Asset'),
        ('other_asset', 'Other Asset'),
    ]
    
    LIABILITY_SUBTYPES = [
        ('current_liability', 'Current Liability'),
        ('client_savings', 'Client Savings'),
        ('borrowings', 'Borrowings'),
        ('other_liability', 'Other Liability'),
    ]
    
    # Primary fields
    code = models.CharField(max_length=20, unique=True, db_index=True)
    name = models.CharField(max_length=200)
    account_type = models.CharField(max_length=20, choices=ACCOUNT_TYPES)
    subtype = models.CharField(max_length=30, blank=True, null=True)
    description = models.TextField()
    
    # Hierarchical structure
    parent_account = models.ForeignKey(
        'self',
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        related_name='child_accounts'
    )
    
    # Status
    is_active = models.BooleanField(default=True)
    is_system_account = models.BooleanField(default=False)
    
    # Metadata
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    
    class Meta:
        db_table = 'accounting_accounts'
        ordering = ['code']
        indexes = [
            models.Index(fields=['account_type', 'is_active']),
            models.Index(fields=['parent_account', 'is_active']),
        ]
    
    def clean(self):
        """
        Validate account code format and positive amounts.
        Requirements: 16.1, 16.2, 16.3
        """
        super().clean()
        
        # Validate code format (must be numeric)
        if self.code:
            if not re.match(r'^\d+$', self.code):
                raise ValidationError({
                    'code': 'Account code must contain only numeric characters.'
                })
            
            # Check minimum code value (codes should be positive)
            try:
                code_value = int(self.code)
                if code_value < 1:
                    raise ValidationError({
                        'code': 'Account code must be a positive number.'
                    })
            except ValueError:
                raise ValidationError({
                    'code': 'Account code must be a valid number.'
                })
    
    def __str__(self):
        return f"{self.code} - {self.name}"


class JournalEntry(models.Model):
    """
    JournalEntry model recording financial transactions with supporting documentation.
    """
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('posted', 'Posted'),
        ('reversed', 'Reversed'),
    ]
    
    # Core fields
    reference_number = models.CharField(max_length=50, unique=True, db_index=True)
    transaction_date = models.DateField(db_index=True)
    description = models.TextField()
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
    
    # Branch association
    branch = models.ForeignKey(
        'users.Branch',
        on_delete=models.CASCADE,
        related_name='journal_entries',
        null=True,
        blank=True
    )
    
    # Integration references
    loan = models.ForeignKey(
        'loans.Loan',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='journal_entries'
    )
    expense = models.ForeignKey(
        'expenses.Expense',
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='journal_entries'
    )
    
    # Reversal tracking
    reverses = models.OneToOneField(
        'self',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='reversed_by'
    )
    
    # Audit trail
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        related_name='created_journal_entries'
    )
    posted_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        related_name='posted_journal_entries'
    )
    posted_at = models.DateTimeField(null=True, blank=True)
    
    class Meta:
        db_table = 'accounting_journal_entries'
        ordering = ['-transaction_date', '-created_at']
        indexes = [
            models.Index(fields=['status', 'transaction_date']),
            models.Index(fields=['branch', 'transaction_date']),
            models.Index(fields=['loan']),
            models.Index(fields=['expense']),
        ]
    
    def clean(self):
        """
        Validate transaction date is not in the future.
        Requirements: 16.5
        """
        super().clean()
        
        if self.transaction_date:
            today = timezone.now().date()
            if self.transaction_date > today:
                raise ValidationError({
                    'transaction_date': 'Transaction date cannot be in the future.'
                })
    
    def __str__(self):
        return f"{self.reference_number} - {self.transaction_date}"


class JournalEntryLine(models.Model):
    """
    JournalEntryLine model representing individual debit/credit lines within a journal entry.
    """
    journal_entry = models.ForeignKey(
        JournalEntry,
        on_delete=models.CASCADE,
        related_name='lines'
    )
    account = models.ForeignKey(
        Account,
        on_delete=models.PROTECT,
        related_name='journal_lines'
    )
    description = models.CharField(max_length=500)
    debit_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0.00'),
        validators=[MinValueValidator(Decimal('0.00'))]
    )
    credit_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0.00'),
        validators=[MinValueValidator(Decimal('0.00'))]
    )
    line_number = models.PositiveIntegerField()
    
    class Meta:
        db_table = 'accounting_journal_entry_lines'
        ordering = ['journal_entry', 'line_number']
        indexes = [
            models.Index(fields=['journal_entry', 'line_number']),
            models.Index(fields=['account']),
        ]
    
    def clean(self):
        """
        Validate debit/credit mutual exclusivity and positive amounts.
        Requirements: 16.3, 16.7
        """
        super().clean()
        
        # Validate that both debit and credit are not filled
        if self.debit_amount and self.credit_amount:
            if self.debit_amount > 0 and self.credit_amount > 0:
                raise ValidationError(
                    'A line item cannot have both debit and credit amounts. '
                    'Please enter only one.'
                )
        
        # Validate amounts are positive
        if self.debit_amount and self.debit_amount < 0:
            raise ValidationError({
                'debit_amount': 'Debit amount must be positive.'
            })
        
        if self.credit_amount and self.credit_amount < 0:
            raise ValidationError({
                'credit_amount': 'Credit amount must be positive.'
            })
        
        # Validate max 2 decimal places
        if self.debit_amount:
            decimal_places = abs(self.debit_amount.as_tuple().exponent)
            if decimal_places > 2:
                raise ValidationError({
                    'debit_amount': 'Debit amount must have no more than 2 decimal places.'
                })
        
        if self.credit_amount:
            decimal_places = abs(self.credit_amount.as_tuple().exponent)
            if decimal_places > 2:
                raise ValidationError({
                    'credit_amount': 'Credit amount must have no more than 2 decimal places.'
                })
    
    def __str__(self):
        return f"{self.journal_entry.reference_number} - Line {self.line_number}"


class GeneralLedger(models.Model):
    """
    GeneralLedger model for posted transactions with running balances.
    """
    account = models.ForeignKey(
        Account,
        on_delete=models.PROTECT,
        related_name='ledger_entries'
    )
    journal_entry = models.ForeignKey(
        JournalEntry,
        on_delete=models.PROTECT,
        related_name='ledger_postings'
    )
    journal_entry_line = models.ForeignKey(
        JournalEntryLine,
        on_delete=models.PROTECT,
        related_name='ledger_posting'
    )
    
    # Transaction details
    transaction_date = models.DateField(db_index=True)
    description = models.TextField()
    reference_number = models.CharField(max_length=50, db_index=True)
    
    # Amounts
    debit_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0.00')
    )
    credit_amount = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=Decimal('0.00')
    )
    balance = models.DecimalField(max_digits=15, decimal_places=2)
    
    # Branch tracking
    branch = models.ForeignKey(
        'users.Branch',
        on_delete=models.CASCADE,
        related_name='ledger_entries',
        null=True,
        blank=True
    )
    
    # Audit
    posted_at = models.DateTimeField(db_index=True)
    posted_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    
    class Meta:
        db_table = 'accounting_general_ledger'
        ordering = ['account', 'transaction_date', 'posted_at']
        indexes = [
            models.Index(fields=['account', 'transaction_date']),
            models.Index(fields=['transaction_date']),
            models.Index(fields=['branch', 'transaction_date']),
            models.Index(fields=['reference_number']),
        ]
    
    def __str__(self):
        return f"{self.account.code} - {self.transaction_date} - {self.reference_number}"


class FiscalPeriod(models.Model):
    """
    FiscalPeriod model defining accounting periods for reporting and controls.
    """
    PERIOD_TYPES = [
        ('monthly', 'Monthly'),
        ('quarterly', 'Quarterly'),
        ('annual', 'Annual'),
    ]
    
    STATUS_CHOICES = [
        ('open', 'Open'),
        ('closed', 'Closed'),
    ]
    
    name = models.CharField(max_length=50, unique=True)
    period_type = models.CharField(max_length=20, choices=PERIOD_TYPES)
    start_date = models.DateField(db_index=True)
    end_date = models.DateField(db_index=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='open')
    
    # Closing information
    closed_at = models.DateTimeField(null=True, blank=True)
    closed_by = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='closed_periods'
    )
    
    # Period balances (cached for performance)
    opening_balances = models.JSONField(default=dict, blank=True)
    closing_balances = 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 = 'accounting_fiscal_periods'
        ordering = ['-start_date']
        indexes = [
            models.Index(fields=['status', 'start_date', 'end_date']),
        ]
    
    def __str__(self):
        return f"{self.name} ({self.period_type})"


class AccountBalance(models.Model):
    """
    AccountBalance model for cached account balances for performance.
    """
    account = models.ForeignKey(
        Account,
        on_delete=models.CASCADE,
        related_name='cached_balances'
    )
    branch = models.ForeignKey(
        'users.Branch',
        on_delete=models.CASCADE,
        null=True,
        blank=True
    )
    as_of_date = models.DateField(db_index=True)
    debit_balance = models.DecimalField(max_digits=15, decimal_places=2)
    credit_balance = models.DecimalField(max_digits=15, decimal_places=2)
    net_balance = models.DecimalField(max_digits=15, decimal_places=2)
    
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'accounting_account_balances'
        unique_together = [['account', 'branch', 'as_of_date']]
        indexes = [
            models.Index(fields=['account', 'as_of_date']),
            models.Index(fields=['as_of_date']),
        ]
    
    def __str__(self):
        branch_str = f" - {self.branch.name}" if self.branch else ""
        return f"{self.account.code} - {self.as_of_date}{branch_str}"


class AuditLog(models.Model):
    """
    AuditLog model for comprehensive audit logging and data integrity tracking.
    Requirements: 14.1, 14.2, 14.3, 14.4, 14.6, 14.7
    """
    ACTION_TYPES = [
        ('create', 'Create'),
        ('modify', 'Modify'),
        ('delete', 'Delete'),
        ('post', 'Post'),
        ('reverse', 'Reverse'),
        ('activate', 'Activate'),
        ('deactivate', 'Deactivate'),
        ('close', 'Close'),
        ('reopen', 'Reopen'),
    ]
    
    # User and timestamp
    user = models.ForeignKey(
        User,
        on_delete=models.SET_NULL,
        null=True,
        related_name='audit_logs'
    )
    timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
    
    # Action details
    action_type = models.CharField(max_length=20, choices=ACTION_TYPES)
    model_name = models.CharField(max_length=100, db_index=True)
    object_id = models.CharField(max_length=100, db_index=True)
    
    # Changes stored as JSON with before/after values
    changes = models.JSONField(default=dict, blank=True)
    
    # Optional description
    description = models.TextField(blank=True)
    
    class Meta:
        db_table = 'accounting_audit_logs'
        ordering = ['-timestamp']
        indexes = [
            models.Index(fields=['timestamp', 'user']),
            models.Index(fields=['model_name', 'object_id']),
            models.Index(fields=['user', 'timestamp']),
        ]
    
    def __str__(self):
        user_str = self.user.get_full_name() if self.user else 'System'
        return f"{user_str} - {self.action_type} - {self.model_name} #{self.object_id}"
