# inventory/models.py
from django.db import models
from decimal import Decimal
from companies.models import Company

import os

def product_image_upload_path(instance, filename):
    company_slug = 'default'
    if getattr(instance, 'company', None) and instance.company.slug:
        company_slug = instance.company.slug
    return os.path.join('products', company_slug, filename)

class ProductCategory(models.Model):
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, null=True, blank=True,
        related_name='categories', db_index=True,
    )
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    color_code = models.CharField(max_length=7, default='#667eea', help_text='Hex color code for category')
    icon = models.CharField(max_length=50, blank=True, help_text='Font Awesome icon class')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'Product Category'
        verbose_name_plural = 'Product Categories'
        ordering = ['name']
        unique_together = [['company', 'name']]  # name unique per company

    def __str__(self):
        return self.name

class Product(models.Model):
    STATUS_CHOICES = [
        ('active', 'Active'),
        ('inactive', 'Inactive'),
        ('discontinued', 'Discontinued'),
    ]
    
    STOCK_STATUS_CHOICES = [
        ('in_stock', 'In Stock'),
        ('low_stock', 'Low Stock'),
        ('out_of_stock', 'Out of Stock'),
    ]
    
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, null=True, blank=True,
        related_name='products', db_index=True,
    )
    name = models.CharField(max_length=200)
    sku = models.CharField(max_length=100, help_text='Stock Keeping Unit')  # unique_together below
    barcode = models.CharField(max_length=100, blank=True, help_text='Product barcode')
    description = models.TextField(blank=True)
    category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='products')
    price = models.DecimalField(max_digits=10, decimal_places=2)

    # NEW: optional wholesale pricing
    wholesale_price = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        blank=True,
        null=True,
        help_text="Wholesale price per unit when selling in bulk"
    )
    wholesale_min_quantity = models.PositiveIntegerField(
        default=0,
        help_text="Minimum quantity in one sale before wholesale price applies. Leave as 0 to disable."
    )

    cost_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
    stock_quantity = models.DecimalField(
        max_digits=12, decimal_places=1, default=0,
        help_text="Supports fractional stock e.g. 0.5 for half a pack."
    )
    minimum_stock = models.PositiveIntegerField(default=10, help_text='Minimum stock level before alert')
    stock_status = models.CharField(max_length=15, choices=STOCK_STATUS_CHOICES, default='in_stock')
    unit_of_measure = models.CharField(max_length=20, default='pcs', help_text='e.g., pcs, kg, liters')
    
    # Additional fields that can be managed via custom fields
    image = models.ImageField(upload_to=product_image_upload_path, blank=True, null=True)
    supplier = models.CharField(max_length=100, blank=True)
    expiry_date = models.DateField(blank=True, null=True)
    weight = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True, help_text='Weight in kg')
    dimensions = models.CharField(max_length=100, blank=True, help_text='LxWxH in cm')
    
    # Status and tracking
    status = models.CharField(max_length=15, choices=STATUS_CHOICES, default='active')
    is_hidden = models.BooleanField(default=False, help_text="Hide product from POS and normal lists")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = 'Product'
        verbose_name_plural = 'Products'
        ordering = ['name']
        unique_together = [['company', 'sku']]  # SKU unique per company
    
    def __str__(self):
        return f"{self.name} ({self.sku})"
    
    def save(self, *args, **kwargs):
        is_new = self.pk is None
        old_status = None
        if not is_new:
            try:
                old_status = Product.objects.get(pk=self.pk).stock_status
            except Product.DoesNotExist:
                pass

        # Update stock status based on quantity
        if self.stock_quantity <= 0:
            self.stock_quantity = 0
            self.stock_status = 'out_of_stock'
        elif self.stock_quantity <= self.minimum_stock:
            self.stock_status = 'low_stock'
        else:
            self.stock_status = 'in_stock'
            
        super().save(*args, **kwargs)
        
        # Trigger Restock Notification synchronously if crossing threshold
        if not is_new and old_status == 'in_stock' and self.stock_status in ['low_stock', 'out_of_stock']:
            try:
                from analytics.models import Notification
                if self.company:
                    Notification.objects.create(
                        company=self.company,
                        title=f"Stock Alert: {self.name}",
                        message=f"Product '{self.name}' has dropped to {self.stock_quantity} units (Minimum: {self.minimum_stock}). Please reorder.",
                        notification_type='warning',
                        icon='fa-box-open',
                        url=f"/inventory/products/{self.pk}/restock/"
                    )
            except Exception as e:
                pass


class BranchStock(models.Model):
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='branch_stocks', db_index=True)
    branch = models.ForeignKey('core.Branch', on_delete=models.CASCADE, related_name='branch_stocks')
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='branch_stocks')
    quantity = models.DecimalField(max_digits=12, decimal_places=1, default=0)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'Branch Stock'
        verbose_name_plural = 'Branch Stocks'
        unique_together = [['branch', 'product']]

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        # Global Synchronizer: Update parent Product stock strictly to the sum of all branches.
        total = self.product.branch_stocks.aggregate(models.Sum('quantity'))['quantity__sum'] or 0
        if self.product.stock_quantity != total:
            self.product.stock_quantity = total
            self.product.save()

    def __str__(self):
        return f"{self.product.name} at {self.branch.name}: {self.quantity}"

# New model for variantâ€‘level branch stock
class VariantBranchStock(models.Model):
    """Branchâ€‘specific stock for a ProductVariant.
    Mirrors BranchStock but points to a variant instead of the base product.
    """
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='variant_branch_stocks', db_index=True)
    branch = models.ForeignKey('core.Branch', on_delete=models.CASCADE, related_name='variant_branch_stocks')
    variant = models.ForeignKey('ProductVariant', on_delete=models.CASCADE, related_name='branch_stocks')
    quantity = models.DecimalField(max_digits=12, decimal_places=1, default=0)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'Variant Branch Stock'
        verbose_name_plural = 'Variant Branch Stocks'
        unique_together = [['branch', 'variant']]

    def __str__(self):
        return f"{self.variant.product.name} ({self.variant.display_label}) at {self.branch.name}: {self.quantity}"

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        # Sync only the PRODUCT's total stock (sum of all variants' stock_quantity).
        # We do NOT overwrite variant.stock_quantity here because:
        #  - variant.stock_quantity = the variant's global/unrestricted stock
        #  - VariantBranchStock.quantity = branch-specific slice of that stock
        # Overwriting would corrupt the global stock when a partial branch restock is done.
        product = self.variant.product
        from django.db.models import Sum as _Sum
        total = product.variants.aggregate(s=_Sum('stock_quantity'))['s'] or 0
        Product.objects.filter(pk=product.pk).update(stock_quantity=total)


class InventoryTransaction(models.Model):
    """
    Track all inventory changes
    """
    TRANSACTION_TYPES = [
        ('in', 'Stock In'),
        ('out', 'Stock Out'),
        ('adjustment', 'Adjustment'),
        ('sale', 'Sale'),
        ('return', 'Return'),
    ]
    
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, null=True, blank=True,
        related_name='inventory_transactions', db_index=True,
    )
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='transactions')
    transaction_type = models.CharField(max_length=15, choices=TRANSACTION_TYPES)
    quantity = models.IntegerField()
    reference = models.CharField(max_length=100, blank=True, help_text='Reference number')
    notes = models.TextField(blank=True)
    created_by = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = 'Inventory Transaction'
        verbose_name_plural = 'Inventory Transactions'
        ordering = ['-created_at']
    
    def __str__(self):
        return f"{self.transaction_type} - {self.quantity} {self.product.name}"


class StockAdjustment(models.Model):
    """
    Track stock adjustments for audit trail
    """
    ADJUSTMENT_TYPES = [
        ('restock', 'Restock'),
        ('reduce', 'Reduce'),
        ('adjustment', 'Manual Adjustment'),
        ('damage', 'Damage/Loss'),
        ('return', 'Return to Stock'),
    ]
    
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, null=True, blank=True,
        related_name='stock_adjustments', db_index=True,
    )
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='stock_adjustments')
    branch = models.ForeignKey('core.Branch', on_delete=models.SET_NULL, null=True, blank=True)
    adjustment_type = models.CharField(max_length=15, choices=ADJUSTMENT_TYPES)
    quantity = models.IntegerField(help_text="Quantity to add/remove")
    reason = models.TextField(blank=True, help_text="Reason for adjustment")
    reference_number = models.CharField(max_length=100, blank=True, help_text="Reference number")
    performed_by = models.CharField(max_length=100, help_text="User who performed adjustment")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        verbose_name = 'Stock Adjustment'
        verbose_name_plural = 'Stock Adjustments'
        ordering = ['-created_at']
    
    def __str__(self):
        return f"{self.adjustment_type} - {self.quantity} {self.product.name}"
    
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs) # Save self first
        
        if self.branch:
            from inventory.models import BranchStock
            bs, _ = BranchStock.objects.get_or_create(
                company=self.company, product=self.product, branch=self.branch, defaults={'quantity': 0}
            )
            if self.adjustment_type in ['restock', 'return']:
                bs.quantity += abs(self.quantity)
            elif self.adjustment_type in ['reduce', 'damage']:
                bs.quantity -= abs(self.quantity)
                if bs.quantity < 0: bs.quantity = 0
            bs.save() # This triggers global save automatically
        else:
            if self.adjustment_type in ['restock', 'return']:
                self.product.stock_quantity += abs(self.quantity)
            elif self.adjustment_type in ['reduce', 'damage']:
                self.product.stock_quantity -= abs(self.quantity)
                if self.product.stock_quantity < 0:
                    self.product.stock_quantity = 0
            self.product.save()


# -----------------------------------
# SUPPLIERS & PURCHASE ORDERS
# -----------------------------------

class Supplier(models.Model):
    company = models.ForeignKey(
        Company, on_delete=models.CASCADE, null=True, blank=True,
        related_name='suppliers', db_index=True,
    )
    name = models.CharField(max_length=200)
    phone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address = models.TextField(blank=True)
    notes = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["name"]

    def __str__(self):
        return self.name

    # Total value of purchase orders
    @property
    def total_purchased(self):
        return sum(po.total_cost for po in self.purchase_orders.all())

    # Total supplier payments
    @property
    def total_paid(self):
        return self.payments.aggregate(
            total=models.Sum("amount")
        )["total"] or Decimal("0")

    # Supplier Balance
    @property
    def balance(self):
        return self.total_purchased - self.total_paid


class PurchaseOrder(models.Model):
    STATUS_CHOICES = (
        ("draft", "Draft"),
        ("ordered", "Ordered"),
        ("received", "Received"),
        ("cancelled", "Cancelled"),
    )

    supplier = models.ForeignKey(
        Supplier,
        on_delete=models.CASCADE,
        related_name="purchase_orders"
    )

    reference_number = models.CharField(max_length=100, blank=True)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft")

    date_ordered = models.DateField(blank=True, null=True)
    date_received = models.DateField(blank=True, null=True)

    notes = models.TextField(blank=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"PO #{self.id} - {self.supplier.name}"

    @property
    def total_cost(self):
        return sum(item.line_total for item in self.items.all())

    def mark_as_received(self, received_by='system', branch=None):
        """
        Mark PO as received, update stock & cost prices, log inventory transactions.
        branch: optional core.Branch instance. If provided, stock goes to BranchStock
                for that branch (auto-syncs product.stock_quantity). If None, updates
                product.stock_quantity directly (backwards-compatible / single-branch).
        """
        from django.utils import timezone as tz
        from inventory.models import InventoryTransaction, BranchStock
        if self.status == 'received':
            return

        self.status = 'received'
        self.date_received = tz.now().date()
        self.save()

        for item in self.items.select_related('product').all():
            product = item.product
            qty = item.quantity or 0
            if qty <= 0:
                continue

            # Always update cost price to latest purchase cost
            product.cost_price = item.unit_cost

            if branch:
                # Branch-specific: BranchStock.save() auto-syncs product.stock_quantity
                bs, _ = BranchStock.objects.get_or_create(
                    company=product.company,
                    branch=branch,
                    product=product,
                    defaults={'quantity': 0},
                )
                bs.quantity += qty
                bs.save()
                product.save(update_fields=['cost_price', 'updated_at'])
                branch_note = f' -> {branch.name}'
            else:
                # Global / no branch: update product directly
                product.stock_quantity = (product.stock_quantity or 0) + qty
                product.save(update_fields=['stock_quantity', 'cost_price', 'updated_at'])
                branch_note = ''

            InventoryTransaction.objects.create(
                company=product.company,
                product=product,
                transaction_type='in',
                quantity=qty,
                reference=f'PO #{self.id} {self.reference_number or ""}',
                notes=f'Goods received from {self.supplier.name}{branch_note}. Unit cost: {item.unit_cost}.',
                created_by=received_by,
            )

        # NOTE: supplier.balance is auto-computed as total_purchased - total_paid.
        # total_purchased sums all PO total_costs, so no extra payment record needed here.

class PurchaseOrderItem(models.Model):
    purchase_order = models.ForeignKey(
        PurchaseOrder,
        on_delete=models.CASCADE,
        related_name="items"
    )

    product = models.ForeignKey("inventory.Product", on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField()
    unit_cost = models.DecimalField(max_digits=12, decimal_places=2)

    @property
    def line_total(self):
        return self.unit_cost * self.quantity

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"

class SupplierPayment(models.Model):
    supplier = models.ForeignKey(
        Supplier,
        on_delete=models.CASCADE,
        related_name="payments"
    )

    amount = models.DecimalField(max_digits=12, decimal_places=2)
    notes = models.TextField(blank=True)
    date = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-date"]

    def __str__(self):
        return f"Payment â‚¦{self.amount} â†’ {self.supplier.name}"


# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
# PRODUCT VARIANTS  (Size / Colour / Style per product)
# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

class ProductVariant(models.Model):
    """
    A specific sellable variation of a Product (e.g. Sneakers - Size 42, Red).
    Each variant has its own price, cost, stock level, SKU, and barcode.
    Price and cost fall back to the parent product if not explicitly set.
    """
    STOCK_STATUS_CHOICES = [
        ('in_stock',    'In Stock'),
        ('low_stock',   'Low Stock'),
        ('out_of_stock','Out of Stock'),
    ]

    company          = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='product_variants', db_index=True)
    product          = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='variants')
    attribute_values = models.JSONField(default=dict, help_text='e.g. {"Size": "XL", "Colour": "Red"}')
    sku              = models.CharField(max_length=100, help_text='Variant-level SKU - unique per company')
    barcode          = models.CharField(max_length=100, blank=True)
    price            = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,
                                           help_text='Leave blank to inherit parent product price')
    cost_price       = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True,
                                           help_text='Leave blank to inherit parent product cost')
    stock_quantity   = models.DecimalField(max_digits=12, decimal_places=1, default=0)
    minimum_stock    = models.PositiveIntegerField(default=5)
    stock_status     = models.CharField(max_length=15, choices=STOCK_STATUS_CHOICES, default='in_stock')
    is_active        = models.BooleanField(default=True)
    created_at       = models.DateTimeField(auto_now_add=True)
    updated_at       = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name        = 'Product Variant'
        verbose_name_plural = 'Product Variants'
        unique_together     = [['company', 'sku']]
        ordering            = ['product', 'sku']

    def __str__(self):
        attrs = ', '.join(f"{k}: {v}" for k, v in self.attribute_values.items())
        return f"{self.product.name} - {attrs} ({self.sku})"

    @property
    def effective_price(self):
        """Return variant price if set, else fall back to parent product price."""
        return self.price if self.price is not None else self.product.price

    @property
    def effective_cost(self):
        return self.cost_price if self.cost_price is not None else self.product.cost_price

    @property
    def display_label(self):
        """Short human-readable label for POS e.g. 'XL / Red'."""
        return ' / '.join(str(v) for v in self.attribute_values.values())

    def save(self, *args, **kwargs):
        if self.stock_quantity <= 0:
            self.stock_quantity = 0
            self.stock_status = 'out_of_stock'
        elif self.stock_quantity <= self.minimum_stock:
            self.stock_status = 'low_stock'
        else:
            self.stock_status = 'in_stock'
        super().save(*args, **kwargs)
        # â”€â”€ Sync parent product total stock â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
        # Parent product.stock_quantity = SUM of all its variants' stock_quantity.
        # This only applies when the product uses variants; if you also track
        # product-level BranchStock independently, you should zero it out and
        # rely solely on variant quantities.
        product = self.product
        total = product.variants.aggregate(models.Sum('stock_quantity'))['stock_quantity__sum'] or 0
        if product.stock_quantity != total:
            # Use update() to avoid recursive save() loop
            Product.objects.filter(pk=product.pk).update(stock_quantity=total)


# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
# INTER-BRANCH STOCK TRANSFERS
# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

class StockTransfer(models.Model):
    """
    Records a formal request to move stock between branches.
    Flow: draft -> approved -> completed (atomic BranchStock debit/credit).
    Each completed transfer writes StockAdjustment records on both branches.
    """
    STATUS_CHOICES = [
        ('draft',     'Draft'),
        ('approved',  'Approved'),
        ('completed', 'Completed'),
        ('cancelled', 'Cancelled'),
    ]

    company        = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='stock_transfers', db_index=True)
    from_branch    = models.ForeignKey('core.Branch', on_delete=models.PROTECT, related_name='transfers_out')
    to_branch      = models.ForeignKey('core.Branch', on_delete=models.PROTECT, related_name='transfers_in')
    status         = models.CharField(max_length=15, choices=STATUS_CHOICES, default='draft')
    reference      = models.CharField(max_length=60, blank=True)
    notes          = models.TextField(blank=True)
    transferred_by = models.CharField(max_length=150)
    approved_by    = models.CharField(max_length=150, blank=True)
    completed_by   = models.CharField(max_length=150, blank=True)
    created_at     = models.DateTimeField(auto_now_add=True, db_index=True)
    approved_at    = models.DateTimeField(null=True, blank=True)
    completed_at   = models.DateTimeField(null=True, blank=True)

    class Meta:
        verbose_name        = 'Stock Transfer'
        verbose_name_plural = 'Stock Transfers'
        ordering            = ['-created_at']

    def __str__(self):
        return f"Transfer {self.reference or self.pk}: {self.from_branch.name} -> {self.to_branch.name} [{self.get_status_display()}]"

    def save(self, *args, **kwargs):
        if not self.reference:
            import uuid
            self.reference = f"TRF-{uuid.uuid4().hex[:8].upper()}"
        super().save(*args, **kwargs)

    def complete(self, completed_by='system'):
        """
        Atomically moves stock from source to destination branch.
        Raises ValueError if source has insufficient stock for any item.
        """
        from django.utils import timezone as tz
        from django.db import transaction as db_transaction

        if self.status != 'approved':
            raise ValueError("Transfer must be in 'Approved' state before completion.")

        with db_transaction.atomic():
            for item in self.items.select_related('product', 'variant').all():
                product = item.product

                src_bs, _ = BranchStock.objects.get_or_create(
                    company=self.company, branch=self.from_branch, product=product,
                    defaults={'quantity': 0}
                )
                if src_bs.quantity < item.quantity:
                    raise ValueError(
                        f"Insufficient stock for '{product.name}' at {self.from_branch.name}. "
                        f"Available: {src_bs.quantity}, Requested: {item.quantity}"
                    )
                src_bs.quantity -= item.quantity
                src_bs.save()

                dst_bs, _ = BranchStock.objects.get_or_create(
                    company=self.company, branch=self.to_branch, product=product,
                    defaults={'quantity': 0}
                )
                dst_bs.quantity += item.quantity
                dst_bs.save()

                if item.variant:
                    item.variant.stock_quantity = max(0, item.variant.stock_quantity - item.quantity)
                    item.variant.save()

                StockAdjustment.objects.create(
                    company=self.company, product=product, branch=self.from_branch,
                    adjustment_type='reduce', quantity=item.quantity,
                    reason=f"Transfer OUT -> {self.to_branch.name} (Ref: {self.reference})",
                    reference_number=self.reference, performed_by=completed_by
                )
                StockAdjustment.objects.create(
                    company=self.company, product=product, branch=self.to_branch,
                    adjustment_type='restock', quantity=item.quantity,
                    reason=f"Transfer IN <- {self.from_branch.name} (Ref: {self.reference})",
                    reference_number=self.reference, performed_by=completed_by
                )

            self.status       = 'completed'
            self.completed_by = completed_by
            self.completed_at = tz.now()
            self.save()


class StockTransferItem(models.Model):
    """A single product line within a StockTransfer."""
    transfer = models.ForeignKey(StockTransfer, on_delete=models.CASCADE, related_name='items')
    product  = models.ForeignKey(Product, on_delete=models.PROTECT)
    variant  = models.ForeignKey(
        ProductVariant, on_delete=models.PROTECT, null=True, blank=True,
        help_text='Optional - only set for variant products'
    )
    quantity = models.PositiveIntegerField()

    class Meta:
        verbose_name = 'Stock Transfer Item'

    def __str__(self):
        v_label = f" [{self.variant.display_label}]" if self.variant else ""
        return f"{self.quantity}x {self.product.name}{v_label}"


# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
# PRODUCT BUNDLES  (Multi-item kits sold as one unit)
# â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€

class ProductBundle(models.Model):
    """
    A named kit of multiple products/variants sold at a single bundle price.
    Stock is decremented per component at point of sale.
    """
    company    = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='product_bundles', db_index=True)
    name       = models.CharField(max_length=200)
    sku        = models.CharField(max_length=100)
    barcode    = models.CharField(max_length=100, blank=True)
    price      = models.DecimalField(max_digits=10, decimal_places=2)
    image      = models.ImageField(upload_to='bundles/', blank=True, null=True)
    notes      = models.TextField(blank=True)
    is_active  = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name        = 'Product Bundle'
        verbose_name_plural = 'Product Bundles'
        unique_together     = [['company', 'sku']]
        ordering            = ['name']

    def __str__(self):
        return f"{self.name} [Bundle - {self.items.count()} components]"


class BundleItem(models.Model):
    """A component product within a ProductBundle."""
    bundle   = models.ForeignKey(ProductBundle, on_delete=models.CASCADE, related_name='items')
    product  = models.ForeignKey(Product, on_delete=models.PROTECT)
    variant  = models.ForeignKey(ProductVariant, on_delete=models.PROTECT, null=True, blank=True)
    quantity = models.PositiveIntegerField(default=1)

    class Meta:
        verbose_name = 'Bundle Item'

    def __str__(self):
        v_label = f" [{self.variant.display_label}]" if self.variant else ""
        return f"{self.quantity}x {self.product.name}{v_label}"
