from datetime import datetime
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator


PromoCodeTypeLiteral = Literal["percent", "fixed", "threshold_fixed", "override"]
PromoCodeScopeLiteral = Literal["all", "category", "group", "product"]
PromoCodeDiscountLevelLiteral = Literal["order", "product"]
PromoCodeClaimMethodLiteral = Literal["code", "mall"]


class PromoCodeCreate(BaseModel):
    code: str = Field(..., min_length=1, max_length=64)
    description: str | None = None
    type: PromoCodeTypeLiteral
    value: int = Field(..., ge=0)
    discount_level: PromoCodeDiscountLevelLiteral = "order"
    scope: PromoCodeScopeLiteral = "all"
    scope_ids: list[int] = Field(default_factory=list)
    starts_at: datetime | None = None
    expires_at: datetime | None = None
    max_uses: int = Field(0, ge=0)
    max_uses_per_user: int = Field(0, ge=0)
    applicable_cycles: list[str] = Field(default_factory=list)
    min_order_amount: int = Field(0, ge=0)
    min_quantity: int = Field(0, ge=0)
    max_discount_amount: int = Field(0, ge=0)
    required_product_ids: list[int] = Field(default_factory=list)
    required_group_ids: list[int] = Field(default_factory=list)
    required_category_ids: list[int] = Field(default_factory=list)
    allowed_user_ids: list[int] = Field(default_factory=list)
    new_users_only: bool = False
    applies_to_renewals: bool = False
    is_stackable: bool = False
    claim_method: PromoCodeClaimMethodLiteral = "code"
    list_on_mall: bool = False
    is_enabled: bool = True

    @field_validator("code", mode="before")
    @classmethod
    def normalize_code(cls, value: str) -> str:
        code = str(value or "").strip().upper()
        if not code:
            raise ValueError("优惠码不能为空")
        return code

    @field_validator("applicable_cycles", mode="before")
    @classmethod
    def normalize_cycles(cls, value):
        if value is None:
            return []
        if isinstance(value, str):
            return [part.strip() for part in value.split(",") if part.strip()]
        return value


class PromoCodeUpdate(BaseModel):
    code: str | None = Field(None, min_length=1, max_length=64)
    description: str | None = None
    type: PromoCodeTypeLiteral | None = None
    value: int | None = Field(None, ge=0)
    discount_level: PromoCodeDiscountLevelLiteral | None = None
    scope: PromoCodeScopeLiteral | None = None
    scope_ids: list[int] | None = None
    starts_at: datetime | None = None
    expires_at: datetime | None = None
    max_uses: int | None = Field(None, ge=0)
    max_uses_per_user: int | None = Field(None, ge=0)
    applicable_cycles: list[str] | None = None
    min_order_amount: int | None = Field(None, ge=0)
    min_quantity: int | None = Field(None, ge=0)
    max_discount_amount: int | None = Field(None, ge=0)
    required_product_ids: list[int] | None = None
    required_group_ids: list[int] | None = None
    required_category_ids: list[int] | None = None
    allowed_user_ids: list[int] | None = None
    new_users_only: bool | None = None
    applies_to_renewals: bool | None = None
    is_stackable: bool | None = None
    claim_method: PromoCodeClaimMethodLiteral | None = None
    list_on_mall: bool | None = None
    is_enabled: bool | None = None

    @field_validator("code", mode="before")
    @classmethod
    def normalize_code(cls, value: str | None) -> str | None:
        if value is None:
            return None
        code = str(value or "").strip().upper()
        if not code:
            raise ValueError("优惠码不能为空")
        return code


class PromoCodeResponse(BaseModel):
    model_config = ConfigDict(from_attributes=True)

    id: int
    code: str
    description: str | None
    issuer_user_id: int | None = None
    issuer_type: str | None = None
    point_cost: int = 0
    point_refunded: int = 0
    type: str
    value: int
    discount_level: str
    scope: str
    scope_ids: list[int]
    starts_at: datetime | None
    expires_at: datetime | None
    max_uses: int
    max_uses_per_user: int
    used_count: int
    applicable_cycles: list[str]
    min_order_amount: int
    min_quantity: int
    max_discount_amount: int
    required_product_ids: list[int]
    required_group_ids: list[int]
    required_category_ids: list[int]
    allowed_user_ids: list[int]
    new_users_only: bool
    applies_to_renewals: bool
    is_stackable: bool
    claim_method: str
    list_on_mall: bool
    is_enabled: bool
    created_at: datetime
    updated_at: datetime


class PromoCodeListResponse(BaseModel):
    items: list[PromoCodeResponse]
    total: int
