"""Marketplace PART 2: Order Extension, Pricing, and Cart Layer.

OWNERSHIP MAP:
    OWNS: OrderItem - line-item extension of orders.Order (RETAIL type)
    OWNS: OrderShipment - merged shipping and tracking record
    OWNS: OrderStatusHistory - append-only order status log
    OWNS: ShoppingCart, CartItem, CartPromotion
    OWNS: Wishlist, WishlistItem
    OWNS: ProductPrice, FlashOffer, CouponCode, CouponUsage

DOES NOT OWN (references only):
    orders.Order
    products.Product
    marketplace.Vendor
    marketplace.ProductVariant
    geo.FranchiseStore
    payments.PaymentTransaction

REMOVED IN CONSOLIDATION:
    FranchiseHead, FranchiseCoverage, FranchiseInventory, FranchiseEarning,
    duplicate Order, duplicate OrderPayment, legacy OrderShipping/OrderTracking.
"""

from decimal import Decimal
from django.db import models
from django.contrib.auth import get_user_model
from django.core.validators import MinValueValidator
import uuid

User = get_user_model()


# =============================================================================
# ORDER EXTENSION LAYER
# These models extend orders.Order for retail marketplace orders.
# orders.Order.order_type = 'RETAIL' enables multi-item marketplace orders.
# orders.Order.order_type = 'BUNDLE' preserves existing bundle flow untouched.
# =============================================================================

class OrderItem(models.Model):
    """
    Individual line item in a retail marketplace order.
    ONLY used when orders.Order.order_type = 'RETAIL'.
    Bundle orders continue using orders.Order.product FK directly (existing flow).

    Fulfillment assignment:
      fulfillment_mode = VENDOR_FULFILLED  → vendor FK is set
      fulfillment_mode = FRANCHISE_FULFILLED → franchise_store FK is set
      fulfillment_mode = COMPANY_FULFILLED → both are null (company warehouse)
    """
    FULFILLMENT_MODE_CHOICES = [
        ('VENDOR_FULFILLED', 'Vendor Fulfilled'),
        ('COMPANY_FULFILLED', 'Company Fulfilled'),
        ('FRANCHISE_FULFILLED', 'Franchise Fulfilled'),
        ('PICKUP_POINT', 'Customer Pickup Point'),
    ]
    ITEM_STATUS_CHOICES = [
        ('PENDING', 'Pending'),
        ('CONFIRMED', 'Confirmed'),
        ('PACKED', 'Packed'),
        ('SHIPPED', 'Shipped'),
        ('DELIVERED', 'Delivered'),
        ('CANCELLED', 'Cancelled'),
        ('RETURNED', 'Returned'),
    ]

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # CORRECTED: FK to existing orders.Order (not a duplicate marketplace.Order)
    order = models.ForeignKey(
        'orders.Order', on_delete=models.CASCADE, related_name='items',
    )
    # CORRECTED: FK to existing products.Product
    product = models.ForeignKey(
        'products.Product', on_delete=models.PROTECT, related_name='order_items',
    )
    variant = models.ForeignKey(
        'marketplace.ProductVariant', on_delete=models.SET_NULL, null=True, blank=True,
        help_text="Set if customer selected a specific variant (size/colour/etc.)",
    )
    quantity = models.PositiveIntegerField(validators=[MinValueValidator(1)])
    unit_price = models.DecimalField(
        max_digits=12, decimal_places=2,
        help_text="Price at time of order (snapshot — never updated after creation)",
    )
    total_price = models.DecimalField(max_digits=12, decimal_places=2)
    discount_amount = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))
    tax_amount = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))

    fulfillment_mode = models.CharField(
        max_length=25, choices=FULFILLMENT_MODE_CHOICES, null=True, blank=True,
        help_text="Determined by FulfillmentService at order creation",
    )
    # CORRECTED: FK to marketplace.Vendor (not FranchiseHead)
    vendor = models.ForeignKey(
        'marketplace.Vendor', on_delete=models.SET_NULL, null=True, blank=True,
        related_name='fulfilled_items',
    )
    # CORRECTED: FK to geo.FranchiseStore (not removed FranchiseHead)
    franchise_store = models.ForeignKey(
        'geo.FranchiseStore', on_delete=models.SET_NULL, null=True, blank=True,
        related_name='fulfilled_order_items',
    )
    item_status = models.CharField(
        max_length=20, choices=ITEM_STATUS_CHOICES, default='PENDING', db_index=True,
    )

    class Meta:
        indexes = [
            models.Index(fields=['order']),
            models.Index(fields=['product', '-id']),
            models.Index(fields=['item_status']),
            models.Index(fields=['vendor']),
            models.Index(fields=['franchise_store']),
        ]

    def __str__(self):
        return f'OrderItem {self.id} — {self.product_id} x{self.quantity}'


class OrderShipment(models.Model):
    """
    Merged model: OrderShipping + ShipmentTracking.
    One shipment record per order (OneToOne).
    Tracks both logistics configuration and live carrier tracking in one place.
    """
    SHIPPING_METHOD_CHOICES = [
        ('STANDARD', 'Standard'),
        ('FAST', 'Fast Delivery'),
        ('EXPRESS', 'Express'),
        ('PICKUP', 'Customer Pickup'),
    ]
    STATUS_CHOICES = [
        ('PENDING', 'Pending'),
        ('PICKED_UP', 'Picked Up'),
        ('IN_TRANSIT', 'In Transit'),
        ('OUT_FOR_DELIVERY', 'Out for Delivery'),
        ('DELIVERED', 'Delivered'),
        ('FAILED', 'Delivery Failed'),
        ('RETURNED', 'Returned'),
    ]

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # CORRECTED: FK to existing orders.Order
    order = models.OneToOneField(
        'orders.Order', on_delete=models.CASCADE, related_name='shipment',
    )
    shipping_method = models.CharField(
        max_length=20, choices=SHIPPING_METHOD_CHOICES, default='STANDARD',
    )
    shipping_amount = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal('0.00'))
    estimated_delivery = models.DateField(null=True, blank=True)
    actual_delivery = models.DateField(null=True, blank=True)

    # Carrier tracking
    carrier_name = models.CharField(max_length=100, blank=True)
    tracking_number = models.CharField(max_length=100, blank=True, db_index=True)
    tracking_url = models.URLField(blank=True, null=True)
    current_location = models.CharField(max_length=255, blank=True)

    status = models.CharField(
        max_length=25, choices=STATUS_CHOICES, default='PENDING', db_index=True,
    )
    picked_up_at = models.DateTimeField(null=True, blank=True)
    delivered_at = models.DateTimeField(null=True, blank=True)
    last_update = models.DateTimeField(auto_now=True)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'Shipment for Order {self.order_id} — {self.status}'


class OrderStatusHistory(models.Model):
    """
    Merged model: OrderTracking + DeliveryStatus.
    Append-only log of all status transitions for an order.
    Covers both internal order status changes and carrier delivery status updates.
    """
    RECORDED_BY_CHOICES = [
        ('SYSTEM', 'System'),
        ('CARRIER', 'Carrier Webhook'),
        ('VENDOR', 'Vendor'),
        ('FRANCHISE', 'Franchise'),
        ('ADMIN', 'Admin'),
    ]

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # CORRECTED: FK to existing orders.Order
    order = models.ForeignKey(
        'orders.Order', on_delete=models.CASCADE, related_name='status_history',
    )
    status = models.CharField(max_length=50)
    description = models.TextField(blank=True)
    location = models.CharField(max_length=255, blank=True)
    failure_reason = models.CharField(max_length=255, blank=True, null=True)
    recorded_by = models.CharField(
        max_length=20, choices=RECORDED_BY_CHOICES, default='SYSTEM',
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['-created_at']
        indexes = [models.Index(fields=['order', '-created_at'])]


# =============================================================================
# SHOPPING CART & WISHLIST
# =============================================================================

class ShoppingCart(models.Model):
    """
    Per-user shopping cart. OneToOne ensures one active cart per user.
    Price validation happens at checkout — CartItem.unit_price is a snapshot
    captured at add-to-cart time and expires after price_expires_at.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='shopping_cart')
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'Cart({self.user_id})'

    @property
    def total_items(self):
        return sum(item.quantity for item in self.items.all())


class CartItem(models.Model):
    """
    Item in a shopping cart.
    unit_price is captured at add-to-cart time.
    price_expires_at: checkout service must re-validate price if now() > price_expires_at.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    cart = models.ForeignKey(ShoppingCart, on_delete=models.CASCADE, related_name='items')
    # CORRECTED: FK to existing products.Product
    product = models.ForeignKey('products.Product', on_delete=models.CASCADE)
    variant = models.ForeignKey(
        'marketplace.ProductVariant', on_delete=models.SET_NULL, null=True, blank=True,
    )
    quantity = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)])
    unit_price = models.DecimalField(
        max_digits=12, decimal_places=2,
        help_text="Price at add-to-cart time. Re-validate at checkout if expired.",
    )
    price_captured_at = models.DateTimeField(
        auto_now_add=True,
        help_text="When unit_price was last captured. Used to detect stale pricing.",
    )
    price_expires_at = models.DateTimeField(
        null=True, blank=True,
        help_text="If set, checkout must re-fetch price if now() > price_expires_at.",
    )
    added_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ('cart', 'product', 'variant')
        indexes = [models.Index(fields=['cart', 'product'])]

    @property
    def total_price(self):
        return self.unit_price * self.quantity


class CartPromotion(models.Model):
    """Snapshot of a coupon applied to a cart. At most one per cart."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    cart = models.ForeignKey(ShoppingCart, on_delete=models.CASCADE, related_name='promotions')
    coupon = models.ForeignKey('marketplace.CouponCode', on_delete=models.PROTECT)
    discount_amount = models.DecimalField(max_digits=12, decimal_places=2)
    applied_at = models.DateTimeField(auto_now_add=True)


class Wishlist(models.Model):
    """Per-user saved product wishlist."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='wishlist')
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f'Wishlist({self.user_id})'


class WishlistItem(models.Model):
    """Product saved to a wishlist."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    wishlist = models.ForeignKey(Wishlist, on_delete=models.CASCADE, related_name='items')
    # CORRECTED: FK to existing products.Product
    product = models.ForeignKey('products.Product', on_delete=models.CASCADE)
    added_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('wishlist', 'product')


# =============================================================================
# PRICING LAYER
# =============================================================================

class ProductPrice(models.Model):
    """
    Dynamic pricing rule for a marketplace product.
    Replaces the old PriceConfiguration model.

    UNIQUE_TOGETHER FIX: The original PriceConfiguration used nullable pincode and
    customer_tier in unique_together, which fails silently with NULL values in SQL.
    This model uses sentinel strings ('__ALL__') to represent "applies globally",
    ensuring the DB-level unique constraint works correctly.

    Price resolution precedence (enforced in PricingService):
      FRANCHISE > PINCODE > TIER > GLOBAL
    When multiple rules match, take the one with the highest priority value.
    """
    PRICE_TYPE_CHOICES = [
        ('GLOBAL', 'Global (all customers, all locations)'),
        ('PINCODE', 'Pincode-specific'),
        ('TIER', 'Customer tier-specific'),
        ('FRANCHISE', 'Franchise-store-specific'),
    ]
    CUSTOMER_TIER_CHOICES = [
        ('GOLD', 'Gold'),
        ('PLATINUM', 'Platinum'),
        ('PREMIUM', 'Premium'),
        ('__ALL__', 'All Tiers'),  # sentinel for non-tier rules
    ]

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # CORRECTED: FK to existing products.Product
    product = models.ForeignKey(
        'products.Product', on_delete=models.CASCADE, related_name='price_rules',
    )
    price_type = models.CharField(max_length=20, choices=PRICE_TYPE_CHOICES, default='GLOBAL')

    # Scope fields — use sentinel '__ALL__' instead of NULL for correct unique_together behaviour
    pincode = models.CharField(
        max_length=10, default='__ALL__',
        help_text="Pincode this price applies to. '__ALL__' = all pincodes.",
    )
    customer_tier = models.CharField(
        max_length=20, choices=CUSTOMER_TIER_CHOICES, default='__ALL__',
        help_text="Customer tier this price applies to. '__ALL__' = all tiers.",
    )
    # CORRECTED: FK to geo.FranchiseStore for FRANCHISE type
    franchise_store = models.ForeignKey(
        'geo.FranchiseStore',
        on_delete=models.SET_NULL, null=True, blank=True,
        related_name='product_prices',
        help_text="Set for FRANCHISE price type. Null for other types.",
    )

    mrp = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    selling_price = models.DecimalField(max_digits=12, decimal_places=2)
    buy_price = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)

    valid_from = models.DateTimeField()
    valid_until = models.DateTimeField()
    is_active = models.BooleanField(default=True)
    priority = models.PositiveIntegerField(
        default=0,
        help_text="Higher value = higher precedence when multiple rules match",
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        # FIXED: unique_together works correctly because pincode/customer_tier use sentinels not NULLs
        unique_together = ('product', 'price_type', 'pincode', 'customer_tier', 'franchise_store')
        indexes = [
            models.Index(fields=['product', 'price_type', 'valid_until']),
            models.Index(fields=['valid_from', 'valid_until']),
            models.Index(fields=['product', 'is_active']),
        ]


class FlashOffer(models.Model):
    """
    Time-limited flash sale for a product.

    RACE CONDITION FIX: Do NOT do read-then-write on usage_count.
    Use atomic F() expression in service:
        updated = FlashOffer.objects.filter(
            id=offer_id, is_active=True,
            usage_count__lt=models.F('max_usage')
        ).update(usage_count=models.F('usage_count') + 1)
        if updated == 0:
            raise OfferExhausted
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # CORRECTED: FK to existing products.Product
    product = models.ForeignKey(
        'products.Product', on_delete=models.CASCADE, related_name='flash_offers',
    )
    title = models.CharField(max_length=255)
    discount_type = models.CharField(
        max_length=20, choices=[('PERCENTAGE', 'Percentage'), ('FIXED', 'Fixed Amount')],
    )
    discount_value = models.DecimalField(max_digits=12, decimal_places=2)
    valid_from = models.DateTimeField(db_index=True)
    valid_until = models.DateTimeField(db_index=True)
    max_usage = models.PositiveIntegerField(
        null=True, blank=True,
        help_text="null = unlimited. Use atomic F() update in service to prevent oversell.",
    )
    usage_count = models.PositiveIntegerField(
        default=0,
        help_text="NEVER increment via read-then-write. Use F('usage_count') + 1 atomically.",
    )
    is_active = models.BooleanField(default=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [
            models.Index(fields=['product', 'is_active', 'valid_until']),
            models.Index(fields=['valid_from', 'valid_until', 'is_active']),
        ]


class CouponCode(models.Model):
    """
    Discount coupon code.

    RACE CONDITION FIX: Do NOT read usage_count and then increment.
    Use atomic conditional update in CouponService.redeem():
        updated = CouponCode.objects.filter(
            id=coupon_id, is_active=True,
            usage_count__lt=models.F('max_usage')
        ).update(usage_count=models.F('usage_count') + 1)
        if updated == 0:
            raise CouponExhaustedOrInactive
    For per-user limit, check CouponUsage table before the atomic update.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    code = models.CharField(max_length=50, unique=True)
    discount_type = models.CharField(
        max_length=20, choices=[('PERCENTAGE', 'Percentage'), ('FIXED', 'Fixed Amount')],
    )
    discount_value = models.DecimalField(max_digits=12, decimal_places=2)
    min_order_amount = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    max_discount = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
    valid_from = models.DateTimeField(db_index=True)
    valid_until = models.DateTimeField(db_index=True)
    max_usage = models.PositiveIntegerField(
        null=True, blank=True,
        help_text="null = unlimited. Use atomic F() update in service to prevent over-redemption.",
    )
    usage_count = models.PositiveIntegerField(
        default=0,
        help_text="NEVER increment via read-then-write. Use F('usage_count') + 1 atomically.",
    )
    max_usage_per_user = models.PositiveIntegerField(default=1)
    is_active = models.BooleanField(default=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [models.Index(fields=['valid_from', 'valid_until', 'is_active'])]


class CouponUsage(models.Model):
    """
    Per-user coupon redemption tracking.
    Used in conjunction with CouponCode.max_usage_per_user enforcement.
    Check this table BEFORE the atomic usage_count increment.
    """
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    coupon = models.ForeignKey(CouponCode, on_delete=models.CASCADE, related_name='usages')
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='coupon_usages')
    # CORRECTED: FK to existing orders.Order
    order = models.ForeignKey(
        'orders.Order', on_delete=models.SET_NULL, null=True, blank=True,
    )
    used_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        indexes = [models.Index(fields=['coupon', 'user'])]

