"""
store/models.py
───────────────
Complete e-commerce data layer for the Tai AfroPress book store.

Models
------
  DeliveryArea       – nairobi CBD, Westlands, etc. with per-area delivery fee
  PickupLocation     – physical offices / agents where customers collect orders
  Coupon             – discount codes (percentage or fixed KES amount)
  Cart               – one cart per authenticated user OR per session (guests)
  CartItem           – one row per book inside a cart
  Order              – a placed order: amounts, delivery info, payment status
  OrderItem          – one row per book inside an order (with price snapshot)
  MpesaTransaction   – immutable audit log of every Daraja API interaction
"""

import uuid
import random
from decimal import Decimal
from datetime import date

from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone

User = get_user_model()


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _generate_order_reference() -> str:
    """
    Generate a human-readable, collision-resistant order reference.
    Format: ORD-YYYYMMDD-XXXXXX  (6 uppercase hex chars from uuid4)
    Example: ORD-20250505-3A7F2B
    """
    today = date.today().strftime("%Y%m%d")
    suffix = uuid.uuid4().hex[:6].upper()
    return f"ORD-{today}-{suffix}"


# ---------------------------------------------------------------------------
# DeliveryArea
# ---------------------------------------------------------------------------

class DeliveryArea(models.Model):
    """
    A named delivery zone within Kenya with a flat delivery fee.
    The `slug` matches the frontend DELIVERY_ZONES id (e.g. "nairobi-cbd").
    The `county` field stores the region name (Nairobi, Central, Coast, …).
    """
    slug = models.SlugField(max_length=100, unique=True, db_index=True)
    name = models.CharField(max_length=255)
    county = models.CharField(max_length=100, help_text="Region / province name, e.g. Nairobi, Central, Coast")
    delivery_fee = models.DecimalField(
        max_digits=8, decimal_places=2, default=Decimal("0.00"),
        help_text="Flat delivery fee in KES for this area."
    )
    estimated_days = models.PositiveSmallIntegerField(
        default=3, help_text="Typical business days from dispatch to delivery."
    )
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["county", "name"]
        verbose_name = "Delivery Area"
        verbose_name_plural = "Delivery Areas"

    def __str__(self) -> str:
        return f"{self.name} ({self.county}) — KES {self.delivery_fee}"


# ---------------------------------------------------------------------------
# PickupLocation
# ---------------------------------------------------------------------------

class PickupLocation(models.Model):
    """A physical location where the customer can collect their order."""
    name = models.CharField(max_length=255)
    address = models.CharField(max_length=512)
    county = models.CharField(max_length=100)
    phone = models.CharField(max_length=20, blank=True)
    opening_hours = models.CharField(
        max_length=100, blank=True,
        help_text="e.g. Mon–Fri 8 am – 6 pm"
    )
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["county", "name"]
        verbose_name = "Pickup Location"
        verbose_name_plural = "Pickup Locations"

    def __str__(self) -> str:
        return f"{self.name} – {self.county}"


# ---------------------------------------------------------------------------
# Coupon
# ---------------------------------------------------------------------------

class Coupon(models.Model):
    DISCOUNT_PERCENTAGE = "percentage"
    DISCOUNT_FIXED = "fixed"
    DISCOUNT_CHOICES = [
        (DISCOUNT_PERCENTAGE, "Percentage (%)"),
        (DISCOUNT_FIXED, "Fixed Amount (KES)"),
    ]

    code = models.CharField(max_length=50, unique=True, db_index=True)
    description = models.CharField(max_length=255, blank=True)
    discount_type = models.CharField(
        max_length=20, choices=DISCOUNT_CHOICES, default=DISCOUNT_PERCENTAGE
    )
    discount_value = models.DecimalField(
        max_digits=8, decimal_places=2,
        help_text="Percentage (0–100) or fixed KES amount."
    )
    min_order_amount = models.DecimalField(
        max_digits=10, decimal_places=2, default=Decimal("0.00"),
        help_text="Minimum cart subtotal required to apply this coupon."
    )
    max_uses = models.PositiveIntegerField(
        null=True, blank=True,
        help_text="Leave blank for unlimited uses."
    )
    uses_count = models.PositiveIntegerField(default=0)
    valid_from = models.DateTimeField(default=timezone.now)
    expires_at = models.DateTimeField(null=True, blank=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["code"]

    def __str__(self) -> str:
        return self.code

    def is_valid(self, order_amount: Decimal = Decimal("0")) -> tuple[bool, str]:
        """
        Returns (True, "Valid") if the coupon can be applied to order_amount,
        or (False, "<reason>") otherwise.
        """
        now = timezone.now()
        if not self.is_active:
            return False, "Coupon is inactive."
        if now < self.valid_from:
            return False, "Coupon is not yet valid."
        if self.expires_at and now > self.expires_at:
            return False, "Coupon has expired."
        if self.max_uses and self.uses_count >= self.max_uses:
            return False, "Coupon usage limit reached."
        if order_amount < self.min_order_amount:
            return False, f"Minimum order amount is KES {self.min_order_amount}."
        return True, "Valid"

    def calculate_discount(self, amount: Decimal) -> Decimal:
        """Return the discount amount to deduct from a given subtotal."""
        if self.discount_type == self.DISCOUNT_PERCENTAGE:
            return (self.discount_value / Decimal("100")) * amount
        # Fixed — never exceed the order amount
        return min(self.discount_value, amount)


# ---------------------------------------------------------------------------
# Cart
# ---------------------------------------------------------------------------

class Cart(models.Model):
    """
    One cart per user (authenticated) or per Django session (guests).
    On login, the guest session cart is merged into the user's cart.
    """
    # Exactly one of these must be set (enforced in view helpers)
    user = models.OneToOneField(
        User, on_delete=models.CASCADE,
        null=True, blank=True, related_name="store_cart"
    )
    session_key = models.CharField(max_length=64, blank=True, db_index=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "Cart"
        verbose_name_plural = "Carts"

    def __str__(self) -> str:
        identifier = self.user.username if self.user else f"session:{self.session_key[:8]}"
        return f"Cart [{identifier}]"

    @property
    def total(self) -> Decimal:
        """Sum of all line totals in this cart."""
        return sum(
            (item.line_total for item in self.items.select_related("book")),
            Decimal("0"),
        )

    @property
    def item_count(self) -> int:
        return sum(item.quantity for item in self.items.all())


# ---------------------------------------------------------------------------
# CartItem
# ---------------------------------------------------------------------------

class CartItem(models.Model):
    cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name="items")
    book = models.ForeignKey(
        "catalog_app.Book", on_delete=models.CASCADE,
        related_name="store_cart_items"
    )
    quantity = models.PositiveIntegerField(default=1)
    added_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (("cart", "book"),)
        ordering = ["added_at"]

    def __str__(self) -> str:
        return f"{self.quantity}× {self.book.title}"

    @property
    def unit_price(self) -> Decimal:
        return self.book.effective_price or Decimal("0")

    @property
    def line_total(self) -> Decimal:
        return (self.book.effective_price or Decimal("0")) * self.quantity


# ---------------------------------------------------------------------------
# Order
# ---------------------------------------------------------------------------

class Order(models.Model):
    # ---- Status choices ------------------------------------------------- #
    STATUS_PENDING = "pending"
    STATUS_PAYMENT_INITIATED = "payment_initiated"
    STATUS_PAID = "paid"
    STATUS_FAILED = "failed"
    STATUS_CANCELLED = "cancelled"
    STATUS_PROCESSING = "processing"
    STATUS_SHIPPED = "shipped"
    STATUS_DELIVERED = "delivered"

    STATUS_CHOICES = [
        (STATUS_PENDING, "Pending"),
        (STATUS_PAYMENT_INITIATED, "Payment Initiated"),
        (STATUS_PAID, "Paid"),
        (STATUS_FAILED, "Payment Failed"),
        (STATUS_CANCELLED, "Cancelled"),
        (STATUS_PROCESSING, "Processing"),
        (STATUS_SHIPPED, "Shipped"),
        (STATUS_DELIVERED, "Delivered"),
    ]

    DELIVERY_HOME = "delivery"
    DELIVERY_PICKUP = "pickup"
    DELIVERY_TYPE_CHOICES = [
        (DELIVERY_HOME, "Home Delivery"),
        (DELIVERY_PICKUP, "Pickup"),
    ]

    # ---- Identity ------------------------------------------------------- #
    order_reference = models.CharField(
        max_length=30, unique=True, db_index=True,
        default=_generate_order_reference,
        help_text="Human-readable unique reference e.g. ORD-20250505-3A7F2B"
    )
    user = models.ForeignKey(
        User, on_delete=models.SET_NULL,
        null=True, blank=True, related_name="store_orders"
    )
    # Guests are identified by session_key; authenticated users may also have one
    # from their pre-login cart session.
    session_key = models.CharField(max_length=64, blank=True, db_index=True)

    # ---- Amounts (all in KES) ------------------------------------------- #
    total_amount = models.DecimalField(
        max_digits=12, decimal_places=2, default=Decimal("0"),
        help_text="Sum of all item line totals (before delivery + discount)."
    )
    delivery_fee = models.DecimalField(
        max_digits=8, decimal_places=2, default=Decimal("0")
    )
    discount_amount = models.DecimalField(
        max_digits=10, decimal_places=2, default=Decimal("0")
    )
    final_amount = models.DecimalField(
        max_digits=12, decimal_places=2, default=Decimal("0"),
        help_text="total_amount + delivery_fee − discount_amount  (charged via M-Pesa)."
    )

    # ---- Delivery ------------------------------------------------------- #
    delivery_type = models.CharField(
        max_length=20, choices=DELIVERY_TYPE_CHOICES, default=DELIVERY_HOME
    )
    delivery_area = models.ForeignKey(
        DeliveryArea, on_delete=models.SET_NULL,
        null=True, blank=True, related_name="orders"
    )
    pickup_location = models.ForeignKey(
        PickupLocation, on_delete=models.SET_NULL,
        null=True, blank=True, related_name="orders"
    )
    # Structured address stored as JSON: {street, area, city, county, postal_code}
    delivery_address = models.JSONField(
        null=True, blank=True,
        help_text="Delivery address snapshot at time of checkout."
    )

    # ---- Customer ------------------------------------------------------- #
    customer_name = models.CharField(max_length=255, blank=True)
    customer_email = models.EmailField(blank=True)
    # Phone number in 254XXXXXXXXX format (normalised before saving)
    phone_number = models.CharField(max_length=20, blank=True)
    notes = models.TextField(blank=True)

    # ---- Payment / M-Pesa ----------------------------------------------- #
    status = models.CharField(
        max_length=30, choices=STATUS_CHOICES, default=STATUS_PENDING, db_index=True
    )
    # Set when STK Push is initiated; used to match the async callback
    checkout_request_id = models.CharField(max_length=100, blank=True, db_index=True)
    merchant_request_id = models.CharField(max_length=100, blank=True)
    # Set when Safaricom callback confirms payment
    mpesa_receipt_number = models.CharField(max_length=50, blank=True, db_index=True)
    transaction_id = models.CharField(max_length=100, blank=True)
    payment_confirmed_at = models.DateTimeField(null=True, blank=True)

    # ---- Paybill manual payment proof ----------------------------------- #
    # Customer submits these after paying manually via M-Pesa Paybill
    paybill_confirmation_code = models.CharField(
        max_length=30, blank=True,
        help_text="M-Pesa transaction code from the customer's confirmation SMS, e.g. NLJ7RT61SV",
    )
    paybill_proof_image = models.FileField(
        upload_to="paybill_proofs/", null=True, blank=True,
        help_text="Screenshot of M-Pesa confirmation message (optional).",
    )
    paybill_proof_submitted_at = models.DateTimeField(null=True, blank=True)

    # ---- Coupon --------------------------------------------------------- #
    coupon = models.ForeignKey(
        Coupon, on_delete=models.SET_NULL,
        null=True, blank=True, related_name="orders"
    )

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "Store Order"
        verbose_name_plural = "Store Orders"

    def __str__(self) -> str:
        name = self.customer_name or "Guest"
        return f"{self.order_reference} — {name} — {self.get_status_display()}"


# ---------------------------------------------------------------------------
# OrderItem
# ---------------------------------------------------------------------------

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items")
    book = models.ForeignKey(
        "catalog_app.Book", on_delete=models.PROTECT,
        related_name="store_order_items"
    )
    unit_price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField()
    total_price = models.DecimalField(max_digits=12, decimal_places=2)
    # Snapshot preserves title/price even if the book is later edited or deleted
    book_snapshot = models.JSONField(
        help_text="Point-in-time snapshot: {id, title, author, isbn, cover_url, price}"
    )

    class Meta:
        ordering = ["id"]

    def __str__(self) -> str:
        return f"{self.quantity}× {self.book.title} ({self.order.order_reference})"


# ---------------------------------------------------------------------------
# PaybillSettings
# ---------------------------------------------------------------------------

class PaybillSettings(models.Model):
    """
    Singleton model for site-level settings editable in the Django admin.
    Covers M-Pesa Paybill details and notification email configuration.
    Edit via Django admin; paybill fields exposed at GET /api/paybill-settings/.
    """
    business_number = models.CharField(
        max_length=50,
        help_text="M-Pesa Paybill business number, e.g. 400200"
    )
    account_number = models.CharField(
        max_length=100,
        help_text="Default account number customers enter, e.g. TAIAFROPRESS"
    )
    admin_notification_email = models.EmailField(
        blank=True,
        default="",
        help_text=(
            "Email address that receives payment-proof notifications. "
            "Leave blank to fall back to the EMAIL_HOST_USER in settings."
        ),
    )
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "Site Settings"
        verbose_name_plural = "Site Settings"

    def __str__(self) -> str:
        return f"Paybill {self.business_number} / {self.account_number}"

    def save(self, *args, **kwargs):
        # Singleton: keep only one row
        self.pk = 1
        super().save(*args, **kwargs)

    @classmethod
    def get(cls) -> "PaybillSettings":
        obj, _ = cls.objects.get_or_create(
            pk=1,
            defaults={"business_number": "400200", "account_number": "TAIAFROPRESS"},
        )
        return obj


# ---------------------------------------------------------------------------
# MpesaTransaction
# ---------------------------------------------------------------------------

class MpesaTransaction(models.Model):
    """
    Immutable audit log.  One row is written when the STK Push is initiated,
    and another when Safaricom's async callback arrives.  Never update rows —
    always INSERT.
    """
    order = models.ForeignKey(
        Order, on_delete=models.CASCADE, related_name="mpesa_transactions"
    )
    checkout_request_id = models.CharField(max_length=100, blank=True, db_index=True)
    merchant_request_id = models.CharField(max_length=100, blank=True)
    phone_number = models.CharField(max_length=20)
    amount = models.DecimalField(max_digits=10, decimal_places=2)

    # Populated from the successful callback
    mpesa_receipt_number = models.CharField(max_length=50, blank=True)
    transaction_date = models.CharField(
        max_length=20, blank=True,
        help_text="Safaricom TransactionDate string e.g. 20250505143022"
    )

    # Result fields (null when this row represents the initial STK request)
    result_code = models.IntegerField(null=True, blank=True)
    result_desc = models.TextField(blank=True)

    # Full raw payload for debugging / dispute resolution
    raw_callback = models.JSONField(null=True, blank=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]
        verbose_name = "M-Pesa Transaction"
        verbose_name_plural = "M-Pesa Transactions"

    def __str__(self) -> str:
        return f"MPesa [{self.checkout_request_id[:20]}] code={self.result_code}"
