# app/modules/loyalty/models/loyalty_transaction.py
"""
Loyalty transaction database model.

Merchant-based transaction tracking:
- Tracks which merchant and store processed each transaction
- Enables chain-wide reporting while maintaining per-location audit trails
- Supports voiding transactions for returns

Records all loyalty events including:
- Stamps earned and redeemed
- Points earned and redeemed
- Welcome bonuses and expirations
- Associated metadata (staff PIN, purchase amount, IP, etc.)
"""

import enum

from sqlalchemy import (
    Column,
    DateTime,
    ForeignKey,
    Index,
    Integer,
    String,
    Text,
)
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import relationship

from app.core.database import Base
from models.database.base import TimestampMixin


class TransactionType(str, enum.Enum):
    """Type of loyalty transaction."""

    # Stamps
    STAMP_EARNED = "stamp_earned"
    STAMP_REDEEMED = "stamp_redeemed"
    STAMP_VOIDED = "stamp_voided"  # Stamps voided due to return

    # Points
    POINTS_EARNED = "points_earned"
    POINTS_REDEEMED = "points_redeemed"
    POINTS_VOIDED = "points_voided"  # Points voided due to return

    # Adjustments (manual corrections by staff)
    STAMP_ADJUSTMENT = "stamp_adjustment"
    POINTS_ADJUSTMENT = "points_adjustment"

    # Card lifecycle
    CARD_CREATED = "card_created"
    CARD_DEACTIVATED = "card_deactivated"
    CARD_REACTIVATED = "card_reactivated"

    # Bonuses and expiration
    WELCOME_BONUS = "welcome_bonus"  # Welcome bonus points on enrollment
    POINTS_EXPIRED = "points_expired"  # Points expired due to inactivity


class LoyaltyTransaction(Base, TimestampMixin):
    """
    Loyalty transaction record.

    Immutable audit log of all loyalty operations for fraud
    detection, analytics, and customer support.

    Tracks which store (location) processed the transaction,
    enabling chain-wide reporting while maintaining per-location
    audit trails.
    """

    __tablename__ = "loyalty_transactions"

    id = Column(Integer, primary_key=True, index=True)

    # Merchant association
    merchant_id = Column(
        Integer,
        ForeignKey("merchants.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
        comment="Merchant that owns the loyalty program",
    )

    # Core relationships
    card_id = Column(
        Integer,
        ForeignKey("loyalty_cards.id", ondelete="CASCADE"),
        nullable=False,
        index=True,
    )
    store_id = Column(
        Integer,
        ForeignKey("stores.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="Store (location) that processed this transaction",
    )
    staff_pin_id = Column(
        Integer,
        ForeignKey("staff_pins.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="Staff PIN used for this operation",
    )
    acting_terminal_device_id = Column(
        Integer,
        ForeignKey("loyalty_terminal_devices.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment=(
            "Paired POS terminal device that performed this transaction "
            "(NULL when the action came from a human user via the web)"
        ),
    )
    category_ids = Column(
        JSON,
        nullable=True,
        comment="List of category IDs selected for this transaction",
    )

    # Related transaction (for voids/returns)
    related_transaction_id = Column(
        Integer,
        ForeignKey("loyalty_transactions.id", ondelete="SET NULL"),
        nullable=True,
        index=True,
        comment="Original transaction (for voids/returns)",
    )

    # =========================================================================
    # Transaction Details
    # =========================================================================
    transaction_type = Column(
        String(30),
        nullable=False,
        index=True,
    )

    # Delta values (positive for earn, negative for redeem/adjustment)
    stamps_delta = Column(
        Integer,
        default=0,
        nullable=False,
        comment="Change in stamps (+1 for earn, -N for redeem)",
    )
    points_delta = Column(
        Integer,
        default=0,
        nullable=False,
        comment="Change in points (+N for earn, -N for redeem)",
    )

    # Balance after transaction (for historical reference)
    stamps_balance_after = Column(
        Integer,
        nullable=True,
        comment="Stamp count after this transaction",
    )
    points_balance_after = Column(
        Integer,
        nullable=True,
        comment="Points balance after this transaction",
    )

    # =========================================================================
    # Purchase Context (for points earned)
    # =========================================================================
    purchase_amount_cents = Column(
        Integer,
        nullable=True,
        comment="Purchase amount in cents (for points calculation)",
    )
    order_reference = Column(
        String(100),
        nullable=True,
        index=True,
        comment="Reference to order that triggered points",
    )

    # =========================================================================
    # Reward Context (for redemptions)
    # =========================================================================
    reward_id = Column(
        String(50),
        nullable=True,
        comment="ID of redeemed reward (from program.points_rewards)",
    )
    reward_description = Column(
        String(255),
        nullable=True,
        comment="Description of redeemed reward",
    )

    # =========================================================================
    # Audit Fields
    # =========================================================================
    ip_address = Column(
        String(45),
        nullable=True,
        comment="IP address of requester (IPv4 or IPv6)",
    )
    user_agent = Column(
        String(500),
        nullable=True,
        comment="User agent string",
    )
    notes = Column(
        Text,
        nullable=True,
        comment="Additional notes (e.g., reason for adjustment)",
    )

    # =========================================================================
    # Timestamps
    # =========================================================================
    transaction_at = Column(
        DateTime(timezone=True),
        nullable=False,
        index=True,
        comment="When the transaction occurred (may differ from created_at)",
    )

    # =========================================================================
    # Relationships
    # =========================================================================
    merchant = relationship("Merchant", backref="loyalty_transactions")
    card = relationship("LoyaltyCard", back_populates="transactions")
    store = relationship("Store", backref="loyalty_transactions")
    staff_pin = relationship("StaffPin", backref="transactions")
    acting_terminal_device = relationship(
        "TerminalDevice",
        backref="transactions",
    )
    related_transaction = relationship(
        "LoyaltyTransaction",
        remote_side=[id],
        backref="voiding_transactions",
    )

    # Indexes
    __table_args__ = (
        Index("idx_loyalty_tx_card_type", "card_id", "transaction_type"),
        Index("idx_loyalty_tx_store_date", "store_id", "transaction_at"),
        Index("idx_loyalty_tx_type_date", "transaction_type", "transaction_at"),
        Index("idx_loyalty_tx_merchant_date", "merchant_id", "transaction_at"),
        Index("idx_loyalty_tx_merchant_store", "merchant_id", "store_id"),
    )

    def __repr__(self) -> str:
        return (
            f"<LoyaltyTransaction(id={self.id}, type='{self.transaction_type}', "
            f"stamps={self.stamps_delta:+d}, points={self.points_delta:+d})>"
        )

    # =========================================================================
    # Properties
    # =========================================================================

    @property
    def is_stamp_transaction(self) -> bool:
        """Check if this is a stamp-related transaction."""
        return self.transaction_type in (
            TransactionType.STAMP_EARNED.value,
            TransactionType.STAMP_REDEEMED.value,
            TransactionType.STAMP_VOIDED.value,
            TransactionType.STAMP_ADJUSTMENT.value,
        )

    @property
    def is_points_transaction(self) -> bool:
        """Check if this is a points-related transaction."""
        return self.transaction_type in (
            TransactionType.POINTS_EARNED.value,
            TransactionType.POINTS_REDEEMED.value,
            TransactionType.POINTS_ADJUSTMENT.value,
            TransactionType.POINTS_VOIDED.value,
            TransactionType.WELCOME_BONUS.value,
            TransactionType.POINTS_EXPIRED.value,
        )

    @property
    def is_earn_transaction(self) -> bool:
        """Check if this is an earning transaction (stamp or points)."""
        return self.transaction_type in (
            TransactionType.STAMP_EARNED.value,
            TransactionType.POINTS_EARNED.value,
            TransactionType.WELCOME_BONUS.value,
        )

    @property
    def is_redemption_transaction(self) -> bool:
        """Check if this is a redemption transaction."""
        return self.transaction_type in (
            TransactionType.STAMP_REDEEMED.value,
            TransactionType.POINTS_REDEEMED.value,
        )

    @property
    def is_void_transaction(self) -> bool:
        """Check if this is a void transaction (for returns)."""
        return self.transaction_type in (
            TransactionType.POINTS_VOIDED.value,
            TransactionType.STAMP_VOIDED.value,
        )

    @property
    def staff_name(self) -> str | None:
        """Get the name of the staff member who performed this transaction."""
        if self.staff_pin:
            return self.staff_pin.name
        return None

    @property
    def store_name(self) -> str | None:
        """Get the name of the store where this transaction occurred."""
        if self.store:
            return self.store.name
        return None
