from django.db import models, transaction
from django.forms import ValidationError
from django.utils.translation import gettext_lazy as _
from datetime import date
from django.contrib.auth.models import User
from products.models import Product, StockMovement, Warehouse
from partners.models import Supplier, Customer, Company
from projects.models import Project
from datetime import *
from django.utils import timezone

# Purchase Order Model
class PurchaseOrder(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('completed', 'Completed'),
        ('cancelled', 'Cancelled'),
    ]

    po_number = models.CharField(max_length=50, unique=True)
    supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE)
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True)  # <-- NEW FIELD
    order_date = models.DateField()
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT, default=1)  # Assuming default warehouse ID is 1
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)


    def __str__(self):
        return f"{self.po_number} - {self.supplier.name}"
    
    def save(self, *args, **kwargs):
        is_completed = self.status == 'completed' and self.pk is not None
        if is_completed:
            original = PurchaseOrder.objects.get(pk=self.pk)
            if original.status != 'completed':  # Only if status changed to completed
                self.create_stock_movements()
        super().save(*args, **kwargs)

    def create_stock_movements(self):
        
        from django.contrib.auth.models import User
        
        for item in self.items.all():  # Assuming you have a related_name='items' on PO items
            StockMovement.objects.create(
                product=item.product,
                warehouse=self.warehouse,  # Assuming PO has a warehouse field
                movement_type='IN',
                quantity=item.quantity,
                purchase_price=item.price,  # From PO item
                reference=f"PO-{self.po_number}",
                notes=f"Received from purchase order {self.po_number}",
                date=timezone.now(),
                # user=self.created_by  # Or request.user if available
            )

    # Add to PurchaseOrder model
    def create_accounting_entries(self):
        """Create journal entries for a completed purchase order"""
        if self.status != 'completed':
            return None
        
        with transaction.atomic():
            # Create journal entry
            je = JournalEntry.objects.create(
                entry_number=f"PO-{self.po_number}",
                date=date.today(),
                description=f"Purchase from {self.supplier.name}",
                status='posted',
                reference=f"PO-{self.po_number}",
                created_by=self.created_by  # Assuming you have this field
            )
            
            # Inventory account (debit)
            inventory_account = Account.objects.get(code='1400')  # Inventory account
            
            # Accounts payable or cash account (credit)
            credit_account = Account.objects.get(code='2010')  # Accounts payable
            
            # Create transactions
            total_amount = sum(item.quantity * item.price for item in self.items.all())
            
            # Debit inventory
            Transaction.objects.create(
                journal_entry=je,
                account=inventory_account,
                debit=total_amount,
                description=f"Inventory purchase from {self.supplier.name}"
            )
            
            # Credit accounts payable
            Transaction.objects.create(
                journal_entry=je,
                account=credit_account,
                credit=total_amount,
                description=f"Purchase from {self.supplier.name}"
            )
            
            return je    

class PurchaseOrderItem(models.Model):
    purchase_order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.DecimalField(max_digits=10, decimal_places=2)
    price = models.DecimalField(max_digits=10, decimal_places=4)

    def __str__(self):
        return f"{self.product.name} ({self.quantity})"
    
    def total_amount(self):
        return self.price * self.quantity


# Accounting Models

class AccountCategory(models.Model):
    """Top-level account categories (Assets, Liabilities, etc.)"""
    name = models.CharField(max_length=100, unique=True)
    normal_balance = models.CharField(max_length=10, choices=[('debit', 'Debit'), ('credit', 'Credit')], default='debit')
    
    def __str__(self):
        return self.name

class AccountSubType(models.Model):
    """Sub-categories (Current Assets, Fixed Assets, etc.)"""
    category = models.ForeignKey(AccountCategory, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    
    class Meta:
        unique_together = ('category', 'name')
    
    def __str__(self):
        return f"{self.category.name} - {self.name}"

class Account(models.Model):
    """Enhanced Chart of Accounts with hierarchy support"""
    code = models.CharField(max_length=20, unique=True , default="1")
    name = models.CharField(max_length=255)
    sub_type = models.ForeignKey(AccountSubType, on_delete=models.PROTECT, default=1)  # Assuming default sub-type ID is 1
    parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
    is_active = models.BooleanField(default=True)
    description = models.TextField(blank=True)
    balance = models.DecimalField(max_digits=15, decimal_places=2, default=0.00)
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        ordering = ['code']
    
    def __str__(self):
        return f"{self.code} - {self.name}"
    
    @property
    def full_name(self):
        if self.parent:
            return f"{self.parent.full_name} > {self.name}"
        return self.name
    
    def update_balance(self):
        """Recalculate balance from all transactions"""
        from django.db.models import Sum
        result = Transaction.objects.filter(account=self).aggregate(
            total_debit=Sum('debit'),
            total_credit=Sum('credit')
        )
        self.balance = (result['total_debit'] or 0) - (result['total_credit'] or 0)
        self.save()

class JournalEntry(models.Model):
    """Enhanced journal entry with status control"""
    ENTRY_STATUS = [
        ('draft', 'Draft'),
        ('posted', 'Posted'),
    ]
    
    entry_number = models.CharField(max_length=20, unique=True, editable=False)
    date = models.DateField()
    description = models.TextField()
    status = models.CharField(max_length=10, choices=ENTRY_STATUS, default='draft')
    reference = models.CharField(max_length=100, blank=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    created_at = models.DateTimeField(auto_now=True)
    updated_at = models.DateTimeField(auto_now=True)
    project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True)
    supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True)
    customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True)
    
    class Meta:
        verbose_name_plural = "Journal Entries"
        ordering = ['-date', '-id']
    
    def __str__(self):
        return f"JE-{self.entry_number} ({self.date})"
    
    def save(self, *args, **kwargs):
        if not self.entry_number:
            # Auto-generate entry number if not provided
            last_entry = JournalEntry.objects.order_by('-id').first()
            last_number = int(last_entry.entry_number.split('-')[1]) if last_entry else 0
            self.entry_number = f"JE-{last_number + 1:04d}"
        super().save(*args, **kwargs)

    def post(self):
        """Post the journal entry and update account balances"""
        if self.status != 'draft':
            raise ValueError("Only draft entries can be posted")
        
        with Transaction.atomic():
            for txn in self.transactions.all():
                txn.account.balance += txn.debit - txn.credit
                txn.account.save()
            self.status = 'posted'
            self.save()
    
    def reverse(self, reversal_date=None):
        """Create a reversing entry"""
        if self.status != 'posted':
            raise ValueError("Only posted entries can be reversed")
        
        reversal_date = reversal_date or self.date
        reversal = JournalEntry.objects.create(
            entry_number=f"REV-{self.entry_number}",
            date=reversal_date,
            description=f"Reversal of {self.entry_number}",
            status='posted',
            reference=f"Reversal of {self.entry_number}",
            created_by=self.created_by,
            project=self.project
        )
        
        for txn in self.transactions.all():
            transaction.objects.create(
                journal_entry=reversal,
                account=txn.account,
                debit=txn.credit,
                credit=txn.debit,
                description=f"Reversal of {self.entry_number}"
            )
        
        self.status = 'reversed'
        self.save()
        return reversal

class Transaction(models.Model):
    """Enhanced transaction model with more details"""
    journal_entry = models.ForeignKey(JournalEntry, on_delete=models.CASCADE, related_name='transactions')
    account = models.ForeignKey(Account, on_delete=models.PROTECT)
    debit = models.DecimalField(max_digits=15, decimal_places=2, default=0.00, null=True, blank=True)
    credit = models.DecimalField(max_digits=15, decimal_places=2, default=0.00, null=True, blank=True)
    description = models.TextField(blank=True)
    reference = models.CharField(max_length=100, blank=True)
    related_object_type = models.CharField(max_length=50, blank=True)  # e.g., 'purchase_order', 'sales_order'
    related_object_id = models.PositiveIntegerField(null=True, blank=True)
    
    class Meta:
        ordering = ['journal_entry__date', 'id']
    
    def __str__(self):
        return f"{self.account.code} - Debit: {self.debit}, Credit: {self.credit}"
    
    def clean(self):
        """Validate that either debit or credit is set, but not both"""
        if self.debit and self.credit:
            raise ValidationError("Transaction cannot have both debit and credit amounts")
        if not self.debit and not self.credit:
            raise ValidationError("Transaction must have either debit or credit amount")        

class AccountingPeriod(models.Model):
    """Track open/closed accounting periods"""
    name = models.CharField(max_length=100)
    start_date = models.DateField()
    end_date = models.DateField()
    is_closed = models.BooleanField(default=False)
    closed_at = models.DateTimeField(null=True, blank=True)
    closed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
    
    class Meta:
        ordering = ['-start_date']
        unique_together = ('start_date', 'end_date')
    
    def __str__(self):
        return f"{self.name} ({self.start_date} to {self.end_date})"
    
    def clean(self):
        if self.start_date >= self.end_date:
            raise ValidationError("End date must be after start date")
        
        # Check for overlapping periods
        overlapping = AccountingPeriod.objects.filter(
            start_date__lte=self.end_date,
            end_date__gte=self.start_date
        ).exclude(pk=self.pk)
        
        if overlapping.exists():
            raise ValidationError("Accounting periods cannot overlap")
    
    def close(self, user):
        """Close the accounting period"""
        if self.is_closed:
            raise ValueError("Period is already closed")
        
        self.is_closed = True
        self.closed_at = timezone.now()
        self.closed_by = user
        self.save()



# Returns purchase and sales

# models.py

class PurchaseReturn(models.Model):
    return_number = models.CharField(max_length=50, unique=True)
    purchase_order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, related_name='returns')
    return_date = models.DateField(default=timezone.now)
    # created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    notes = models.TextField(blank=True, null=True)

    def __str__(self):
        return f"Return {self.return_number} for {self.purchase_order.po_number}"

class PurchaseReturnItem(models.Model):
    return_entry = models.ForeignKey(PurchaseReturn, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=10, decimal_places=2)
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def total_amount(self):
        return self.quantity * self.price



