import enum
from datetime import datetime

from sqlalchemy import BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column

from app.models.base import BaseModel


class PromoCodeType(str, enum.Enum):
    PERCENT = "percent"  # 百分比折扣
    FIXED = "fixed"  # 固定金额(分)
    THRESHOLD_FIXED = "threshold_fixed"  # 满减：满 threshold_amount 减 value（分）
    OVERRIDE = "override"  # 直接覆盖为固定价(分)


class PromoCodeClaimMethod(str, enum.Enum):
    CODE = "code"  # 优惠码直接使用
    MALL = "mall"  # 积分商城领取


class PromoCodeScope(str, enum.Enum):
    ALL = "all"  # 全场
    CATEGORY = "category"  # 限定分类
    GROUP = "group"  # 限定产品组
    PRODUCT = "product"  # 限定产品


class PromoCodeDiscountLevel(str, enum.Enum):
    ORDER = "order"  # 订单级，优惠作用于整张订单
    PRODUCT = "product"  # 产品级，必须选择一个购物车商品行


class PromoCode(BaseModel):
    """优惠码。下单时填写，应用于订单总额或单个订单项。"""

    __tablename__ = "promo_codes"

    code: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    issuer_user_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True
    )
    issuer_type: Mapped[str | None] = mapped_column(String(16), nullable=True, index=True)
    point_cost: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    point_refunded: Mapped[int] = mapped_column(Integer, default=0, nullable=False)

    type: Mapped[PromoCodeType] = mapped_column(String(16), nullable=False)
    value: Mapped[int] = mapped_column(
        Integer, nullable=False, comment="percent: 1-100; fixed/override: 分"
    )

    scope: Mapped[PromoCodeScope] = mapped_column(
        String(16), default=PromoCodeScope.ALL.value, nullable=False
    )
    discount_level: Mapped[PromoCodeDiscountLevel] = mapped_column(
        String(16), default=PromoCodeDiscountLevel.ORDER.value, nullable=False
    )
    # 命中范围 ID 数组：[1,2,3]，对应 scope_type 下的 ID
    scope_ids: Mapped[str | None] = mapped_column(Text, nullable=True)

    # 起止时间
    starts_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    # 使用限制
    max_uses: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="0=不限")
    max_uses_per_user: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    used_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)

    # 适用周期：JSON 数组，如 ["monthly","annually"]；NULL=任意周期
    applicable_cycles: Mapped[str | None] = mapped_column(String(255), nullable=True)

    # 订单门槛（分），低于此金额不生效
    min_order_amount: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    # 最低购买数量（购物车选中商品总数量），0=不限
    min_quantity: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    # 最高优惠金额（分），0=不限，常用于百分比券封顶
    max_discount_amount: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    # 条件限制：订单必须包含指定对象，JSON 数组；空=不限
    required_product_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
    required_group_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
    required_category_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
    # 指定用户 ID 可用，JSON 数组；空=不限
    allowed_user_ids: Mapped[str | None] = mapped_column(Text, nullable=True)

    # 是否仅新用户
    new_users_only: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    # 是否对续费生效
    applies_to_renewals: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    # 是否可与其他优惠叠加
    is_stackable: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)

    # 领取方式
    claim_method: Mapped[PromoCodeClaimMethod] = mapped_column(
        String(16), default=PromoCodeClaimMethod.CODE.value, nullable=False
    )
    # 是否上架积分商城
    list_on_mall: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, comment="是否上架积分商城")

    is_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
