from django.db import models
from django.utils import timezone
from accounts.models import User


class Category(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)

    class Meta:
        verbose_name_plural = 'Categories'

    def __str__(self):
        return self.name


class Branch(models.Model):
    """Business branch/campus that owns one or more stock locations."""

    name = models.CharField(max_length=120, unique=True)
    code = models.CharField(max_length=20, unique=True, null=True, blank=True)
    location = models.CharField(max_length=255, blank=True)
    contact_phone = models.CharField(max_length=30, blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("name",)

    def __str__(self):
        return self.name


class StockLocation(models.Model):
    """Physical/virtual stock locations (Main Store, Kitchen Store, etc.)."""

    branch = models.ForeignKey(
        Branch, on_delete=models.PROTECT,
        related_name='stock_locations'
    )
    name = models.CharField(max_length=120, unique=True)
    code = models.CharField(max_length=20, blank=True, unique=True)
    description = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("name",)

    def __str__(self):
        return self.name


class UserStoreAssignment(models.Model):
    """Assign a user (typically storekeeper) to a primary stock location."""

    user = models.OneToOneField(
        User, on_delete=models.CASCADE, related_name='store_assignment'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT, related_name='assigned_users'
    )
    is_active = models.BooleanField(default=True)
    assigned_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        verbose_name = "User Store Assignment"
        verbose_name_plural = "User Store Assignments"

    def __str__(self):
        return f"{self.user.username} -> {self.location.name}"


class Product(models.Model):
    class UnitChoices(models.TextChoices):
        PIECE = 'piece', 'Piece'
        KG = 'kg', 'Kilogram'
        LITRE = 'litre', 'Litre'
        BOX = 'box', 'Box'
        DOZEN = 'dozen', 'Dozen'
        PACK = 'pack', 'Pack'

    category = models.ForeignKey(
        Category, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='products'
    )
    name = models.CharField(max_length=255)
    sku = models.CharField(max_length=100, unique=True, blank=True)  # barcode / code
    barcode = models.CharField(max_length=100, blank=True, db_index=True)
    unit = models.CharField(max_length=20, choices=UnitChoices.choices, default=UnitChoices.PIECE)
    cost_price = models.DecimalField(max_digits=15, decimal_places=2, default=0)   # buying price
    selling_price = models.DecimalField(max_digits=15, decimal_places=2, default=0) # selling price
    stock_quantity = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    low_stock_alert = models.DecimalField(max_digits=15, decimal_places=2, default=5)
    overstock_alert = models.DecimalField(
        max_digits=15, decimal_places=2, default=0
    )
    track_expiry = models.BooleanField(default=False)
    track_serial = models.BooleanField(default=False)
    image = models.ImageField(upload_to='products/', blank=True, null=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

    @property
    def is_low_stock(self):
        return self.stock_quantity <= self.low_stock_alert

    @property
    def stock_value(self):
        return self.stock_quantity * self.cost_price

    @property
    def has_expired_batches(self):
        return self.batches.filter(
            expiry_date__lt=timezone.now().date(),
            quantity__gt=0,
            is_active=True,
        ).exists()

    @property
    def is_overstock(self):
        return bool(self.overstock_alert and self.stock_quantity >= self.overstock_alert)


class ProductStock(models.Model):
    """Stock balance per product per location."""

    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name='location_stocks'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT, related_name='product_stocks'
    )
    quantity = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ('product', 'location')

    def __str__(self):
        return f"{self.product.name} @ {self.location.name}"


class ProductBatch(models.Model):
    """Batch tracking for expiry-sensitive products."""

    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name='batches'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT,
        related_name='batches', null=True, blank=True
    )
    batch_number = models.CharField(max_length=100)
    expiry_date = models.DateField(null=True, blank=True)
    quantity = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    unit_cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    received_date = models.DateField(default=timezone.now)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('product', 'location', 'batch_number')
        ordering = ('expiry_date', 'batch_number')

    def __str__(self):
        return f"{self.product.name} [{self.batch_number}]"

    @property
    def is_expired(self):
        return bool(self.expiry_date and self.expiry_date < timezone.now().date())


class ProductSerial(models.Model):
    class Status(models.TextChoices):
        IN_STOCK = 'in_stock', 'In Stock'
        SOLD = 'sold', 'Sold'
        DAMAGED = 'damaged', 'Damaged'
        RETURNED = 'returned', 'Returned'

    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name='serials'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT,
        related_name='serials', null=True, blank=True
    )
    serial_number = models.CharField(max_length=150, unique=True)
    batch = models.ForeignKey(
        ProductBatch, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='serials'
    )
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.IN_STOCK
    )
    note = models.CharField(max_length=255, blank=True)
    received_date = models.DateField(default=timezone.now)
    sold_date = models.DateField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.serial_number


class StockMovement(models.Model):
    class MovementType(models.TextChoices):
        STOCK_IN = 'stock_in', 'Stock In'
        STOCK_OUT = 'stock_out', 'Stock Out'
        SALE = 'sale', 'Sale'
        ADJUSTMENT = 'adjustment', 'Adjustment'
        RETURN = 'return', 'Return'
        TRANSFER_OUT = 'transfer_out', 'Transfer Out'
        TRANSFER_IN = 'transfer_in', 'Transfer In'
        EXPIRED = 'expired', 'Expired'
        DAMAGED = 'damaged', 'Damaged'
        LOST = 'lost', 'Lost'

    product = models.ForeignKey(
        Product, on_delete=models.PROTECT, related_name='movements'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT,
        related_name='movements', null=True, blank=True
    )
    batch = models.ForeignKey(
        ProductBatch, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='movements'
    )
    serial = models.ForeignKey(
        ProductSerial, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='movements'
    )
    movement_type = models.CharField(max_length=20, choices=MovementType.choices)
    quantity = models.DecimalField(max_digits=15, decimal_places=2)
    cost_price = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    reference = models.CharField(max_length=100, blank=True)   # sale #, PO #, etc
    note = models.TextField(blank=True)
    is_approved = models.BooleanField(default=True)
    approved_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='approved_stock_movements'
    )
    approved_at = models.DateTimeField(null=True, blank=True)
    date = models.DateField(default=timezone.now)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.movement_type} | {self.product.name} | {self.quantity}"


class Supplier(models.Model):
    name = models.CharField(max_length=255)
    phone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address = models.TextField(blank=True)
    note = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

    @property
    def total_purchases(self):
        return sum(po.total for po in self.purchase_orders.filter(
            status='received'
        ))

    @property
    def unpaid_balance(self):
        return sum(
            po.balance_due
            for po in self.purchase_orders.filter(status='received')
        )


class PurchaseOrder(models.Model):
    class Status(models.TextChoices):
        PENDING = 'pending', 'Pending'
        RECEIVED = 'received', 'Received'
        CANCELLED = 'cancelled', 'Cancelled'

    reference = models.CharField(max_length=50, unique=True)  # PO-0001
    supplier_invoice_number = models.CharField(max_length=100, blank=True)
    supplier = models.ForeignKey(
        Supplier, on_delete=models.PROTECT, related_name='purchase_orders'
    )
    account = models.ForeignKey(
        'finance.Account', on_delete=models.PROTECT,
        null=True, blank=True, related_name='purchase_orders'
    )
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.PENDING
    )
    note = models.TextField(blank=True)
    date = models.DateField(default=timezone.now)
    due_date = models.DateField(null=True, blank=True)
    amount_paid = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    received_date = models.DateField(null=True, blank=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.reference

    @property
    def total(self):
        return sum(item.line_total for item in self.items.all())

    @property
    def balance_due(self):
        balance = self.total - self.amount_paid
        return balance if balance > 0 else 0

    @property
    def payment_status(self):
        if self.amount_paid <= 0:
            return 'unpaid'
        if self.amount_paid >= self.total:
            return 'paid'
        return 'partial'


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

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"

    @property
    def line_total(self):
        return self.quantity * self.unit_cost


class PurchaseReturn(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        PROCESSED = 'processed', 'Processed'
        CANCELLED = 'cancelled', 'Cancelled'

    reference = models.CharField(max_length=50, unique=True)
    purchase_order = models.ForeignKey(
        PurchaseOrder, on_delete=models.PROTECT, related_name='returns'
    )
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.DRAFT
    )
    reason = models.TextField(blank=True)
    date = models.DateField(default=timezone.now)
    processed_date = models.DateField(null=True, blank=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='purchase_returns_created'
    )
    processed_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='purchase_returns_processed'
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-date', '-created_at')

    def __str__(self):
        return self.reference

    @property
    def total(self):
        return sum(item.line_total for item in self.items.all())


class PurchaseReturnItem(models.Model):
    purchase_return = models.ForeignKey(
        PurchaseReturn, on_delete=models.CASCADE, related_name='items'
    )
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=15, decimal_places=2)
    unit_cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"

    @property
    def line_total(self):
        return self.quantity * self.unit_cost


class StockTransfer(models.Model):
    class Status(models.TextChoices):
        REQUESTED = 'requested', 'Requested'
        APPROVED = 'approved', 'Approved'
        REJECTED = 'rejected', 'Rejected'
        COMPLETED = 'completed', 'Completed'

    reference = models.CharField(max_length=50, unique=True)
    from_location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT, related_name='outgoing_transfers'
    )
    to_location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT, related_name='incoming_transfers'
    )
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.REQUESTED
    )
    note = models.TextField(blank=True)
    requested_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='requested_transfers'
    )
    approved_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='approved_transfers'
    )
    requested_at = models.DateTimeField(auto_now_add=True)
    approved_at = models.DateTimeField(null=True, blank=True)
    completed_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ('-requested_at',)

    def __str__(self):
        return self.reference

    @property
    def total_quantity(self):
        return sum(item.quantity for item in self.items.all())


class StockTransferItem(models.Model):
    transfer = models.ForeignKey(
        StockTransfer, on_delete=models.CASCADE, related_name='items'
    )
    product = models.ForeignKey(
        Product, on_delete=models.PROTECT, related_name='transfer_items'
    )
    quantity = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    batch = models.ForeignKey(
        ProductBatch, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='transfer_items'
    )
    serial = models.ForeignKey(
        ProductSerial, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='transfer_items'
    )

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"


class StockAdjustmentRequest(models.Model):
    class Reason(models.TextChoices):
        DAMAGED = 'damaged', 'Damaged'
        LOST = 'lost', 'Lost'
        EXPIRED = 'expired', 'Expired'
        CORRECTION = 'correction', 'Correction'

    class Status(models.TextChoices):
        PENDING = 'pending', 'Pending'
        APPROVED = 'approved', 'Approved'
        REJECTED = 'rejected', 'Rejected'

    product = models.ForeignKey(
        Product, on_delete=models.PROTECT, related_name='adjustment_requests'
    )
    location = models.ForeignKey(
        StockLocation, on_delete=models.PROTECT,
        related_name='adjustment_requests', null=True, blank=True
    )
    quantity = models.DecimalField(max_digits=15, decimal_places=2)
    reason = models.CharField(max_length=20, choices=Reason.choices)
    note = models.TextField(blank=True)
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.PENDING
    )
    requested_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='stock_adjustment_requests'
    )
    approved_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='approved_stock_adjustments'
    )
    created_at = models.DateTimeField(auto_now_add=True)
    approved_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ('-created_at',)

    def __str__(self):
        return f"{self.product.name} | {self.reason} | {self.quantity}"


class Asset(models.Model):
    class Status(models.TextChoices):
        ACTIVE = 'active', 'Active'
        MAINTENANCE = 'maintenance', 'Maintenance'
        RETIRED = 'retired', 'Retired'
        LOST = 'lost', 'Lost'

    name = models.CharField(max_length=255)
    code = models.CharField(max_length=80, unique=True)
    category = models.CharField(max_length=100, blank=True)
    serial_number = models.CharField(max_length=120, blank=True)
    location = models.ForeignKey(
        StockLocation, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='assets'
    )
    purchase_date = models.DateField(null=True, blank=True)
    purchase_cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    useful_life_years = models.PositiveIntegerField(default=5)
    salvage_value = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.ACTIVE
    )
    note = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('name',)

    def __str__(self):
        return self.name

    @property
    def annual_depreciation(self):
        if self.useful_life_years <= 0:
            return 0
        return (self.purchase_cost - self.salvage_value) / self.useful_life_years


class AssetAssignment(models.Model):
    asset = models.ForeignKey(
        Asset, on_delete=models.CASCADE, related_name='assignments'
    )
    assigned_to = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='asset_assignments'
    )
    assigned_date = models.DateField(default=timezone.now)
    returned_date = models.DateField(null=True, blank=True)
    note = models.TextField(blank=True)

    class Meta:
        ordering = ('-assigned_date',)

    def __str__(self):
        return f"{self.asset.name} -> {self.assigned_to}"


class AssetMaintenance(models.Model):
    asset = models.ForeignKey(
        Asset, on_delete=models.CASCADE, related_name='maintenance_logs'
    )
    description = models.CharField(max_length=255)
    cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    service_date = models.DateField(default=timezone.now)
    next_due_date = models.DateField(null=True, blank=True)
    performed_by = models.CharField(max_length=255, blank=True)
    note = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-service_date',)

    def __str__(self):
        return f"{self.asset.name} maintenance on {self.service_date}"
