import json
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.deps import require_role
from app.database import get_db
from app.models.promo_code import PromoCode
from app.models.promo_code_usage import PromoCodeUsage
from app.models.user import User
from app.schemas.common import ApiResponse
from app.schemas.promo_code import (
    PromoCodeCreate,
    PromoCodeListResponse,
    PromoCodeResponse,
    PromoCodeUpdate,
)

router = APIRouter()
_admin = require_role("admin", "super_admin")


def _enum_value(value: Any) -> str:
    return value.value if hasattr(value, "value") else str(value)


def _parse_json_list(raw: str | None) -> list[Any]:
    if not raw:
        return []
    try:
        data = json.loads(raw)
        return data if isinstance(data, list) else []
    except Exception:
        return []


def _parse_scope_ids(raw: str | None) -> list[int]:
    result: list[int] = []
    for item in _parse_json_list(raw):
        try:
            result.append(int(item))
        except (TypeError, ValueError):
            continue
    return result


def _parse_cycles(raw: str | None) -> list[str]:
    return [str(item) for item in _parse_json_list(raw) if str(item).strip()]


def _dump_list(values: list[Any] | None) -> str | None:
    values = values or []
    return json.dumps(values, ensure_ascii=False) if values else None


def _to_response(coupon: PromoCode) -> PromoCodeResponse:
    return PromoCodeResponse(
        id=coupon.id,
        code=coupon.code,
        description=coupon.description,
        issuer_user_id=getattr(coupon, "issuer_user_id", None),
        issuer_type=getattr(coupon, "issuer_type", None),
        point_cost=int(getattr(coupon, "point_cost", 0) or 0),
        point_refunded=int(getattr(coupon, "point_refunded", 0) or 0),
        type=_enum_value(coupon.type),
        value=int(coupon.value or 0),
        discount_level=_enum_value(getattr(coupon, "discount_level", "order")),
        scope=_enum_value(coupon.scope),
        scope_ids=_parse_scope_ids(coupon.scope_ids),
        starts_at=coupon.starts_at,
        expires_at=coupon.expires_at,
        max_uses=int(coupon.max_uses or 0),
        max_uses_per_user=int(coupon.max_uses_per_user or 0),
        used_count=int(coupon.used_count or 0),
        applicable_cycles=_parse_cycles(coupon.applicable_cycles),
        min_order_amount=int(coupon.min_order_amount or 0),
        min_quantity=int(getattr(coupon, "min_quantity", 0) or 0),
        max_discount_amount=int(getattr(coupon, "max_discount_amount", 0) or 0),
        required_product_ids=_parse_scope_ids(getattr(coupon, "required_product_ids", None)),
        required_group_ids=_parse_scope_ids(getattr(coupon, "required_group_ids", None)),
        required_category_ids=_parse_scope_ids(getattr(coupon, "required_category_ids", None)),
        allowed_user_ids=_parse_scope_ids(getattr(coupon, "allowed_user_ids", None)),
        new_users_only=bool(coupon.new_users_only),
        applies_to_renewals=bool(coupon.applies_to_renewals),
        is_stackable=bool(coupon.is_stackable),
        claim_method=_enum_value(getattr(coupon, "claim_method", "code")),
        list_on_mall=bool(getattr(coupon, "list_on_mall", False)),
        is_enabled=bool(coupon.is_enabled),
        created_at=coupon.created_at,
        updated_at=coupon.updated_at,
    )


async def _get_coupon(db: AsyncSession, coupon_id: int) -> PromoCode:
    coupon = await db.get(PromoCode, coupon_id)
    if not coupon:
        raise HTTPException(status_code=404, detail="优惠券不存在")
    return coupon


async def _ensure_code_unique(db: AsyncSession, code: str, *, exclude_id: int | None = None) -> None:
    stmt = select(PromoCode.id).where(func.upper(PromoCode.code) == code.upper())
    if exclude_id is not None:
        stmt = stmt.where(PromoCode.id != exclude_id)
    result = await db.execute(stmt)
    if result.scalar_one_or_none():
        raise HTTPException(status_code=400, detail="优惠码已存在")


def _validate_payload(data: PromoCodeCreate | PromoCodeUpdate) -> None:
    payload = data.model_dump(exclude_unset=True)
    coupon_type = payload.get("type")
    value = payload.get("value")
    starts_at = payload.get("starts_at")
    expires_at = payload.get("expires_at")
    if coupon_type == "percent" and value is not None and value > 100:
        raise HTTPException(status_code=400, detail="百分比优惠券的数值不能超过 100")
    if starts_at and expires_at and starts_at >= expires_at:
        raise HTTPException(status_code=400, detail="结束时间必须晚于开始时间")
    scope = payload.get("scope")
    scope_ids = payload.get("scope_ids")
    if scope and scope != "all" and scope_ids is not None and len(scope_ids) == 0:
        raise HTTPException(status_code=400, detail="限定范围优惠券需要选择适用对象")


def _apply_payload(coupon: PromoCode, data: PromoCodeCreate | PromoCodeUpdate) -> None:
    payload = data.model_dump(exclude_unset=True)
    for field in [
        "code",
        "description",
        "type",
        "value",
        "discount_level",
        "scope",
        "starts_at",
        "expires_at",
        "max_uses",
        "max_uses_per_user",
        "min_order_amount",
        "min_quantity",
        "max_discount_amount",
        "new_users_only",
        "applies_to_renewals",
        "is_stackable",
        "is_enabled",
    ]:
        if field in payload:
            setattr(coupon, field, payload[field])
    if "scope_ids" in payload:
        coupon.scope_ids = _dump_list([int(item) for item in (payload["scope_ids"] or [])])
    if "applicable_cycles" in payload:
        coupon.applicable_cycles = _dump_list([str(item) for item in (payload["applicable_cycles"] or [])])
    for field in [
        "required_product_ids",
        "required_group_ids",
        "required_category_ids",
        "allowed_user_ids",
    ]:
        if field in payload:
            setattr(coupon, field, _dump_list([int(item) for item in (payload[field] or [])]))


@router.get("", response_model=ApiResponse[PromoCodeListResponse])
async def list_promo_codes(
    keyword: str | None = Query(None),
    discount_level: str | None = Query(None),
    enabled: bool | None = Query(None),
    offset: int = Query(0, ge=0),
    limit: int = Query(20, ge=1, le=100),
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    conditions = []
    if keyword:
        pattern = f"%{keyword.strip()}%"
        conditions.append(or_(PromoCode.code.ilike(pattern), PromoCode.description.ilike(pattern)))
    if discount_level:
        conditions.append(PromoCode.discount_level == discount_level)
    if enabled is not None:
        conditions.append(PromoCode.is_enabled.is_(enabled))

    count_stmt = select(func.count()).select_from(PromoCode)
    stmt = select(PromoCode).order_by(PromoCode.id.desc()).offset(offset).limit(limit)
    if conditions:
        count_stmt = count_stmt.where(*conditions)
        stmt = stmt.where(*conditions)

    total = int((await db.execute(count_stmt)).scalar_one() or 0)
    result = await db.execute(stmt)
    items = [_to_response(coupon) for coupon in result.scalars().all()]
    return ApiResponse(data=PromoCodeListResponse(items=items, total=total))


@router.post("", response_model=ApiResponse[PromoCodeResponse])
async def create_promo_code(
    data: PromoCodeCreate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    _validate_payload(data)
    await _ensure_code_unique(db, data.code)
    coupon = PromoCode(
        code=data.code,
        description=data.description,
        type=data.type,
        value=data.value,
        discount_level=data.discount_level,
        scope=data.scope,
        scope_ids=_dump_list(data.scope_ids),
        starts_at=data.starts_at,
        expires_at=data.expires_at,
        max_uses=data.max_uses,
        max_uses_per_user=data.max_uses_per_user,
        used_count=0,
        applicable_cycles=_dump_list(data.applicable_cycles),
        min_order_amount=data.min_order_amount,
        min_quantity=data.min_quantity,
        max_discount_amount=data.max_discount_amount,
        required_product_ids=_dump_list(data.required_product_ids),
        required_group_ids=_dump_list(data.required_group_ids),
        required_category_ids=_dump_list(data.required_category_ids),
        allowed_user_ids=_dump_list(data.allowed_user_ids),
        new_users_only=data.new_users_only,
        applies_to_renewals=data.applies_to_renewals,
        is_stackable=data.is_stackable,
        is_enabled=data.is_enabled,
    )
    db.add(coupon)
    await db.commit()
    await db.refresh(coupon)
    return ApiResponse(data=_to_response(coupon))


@router.get("/{coupon_id}", response_model=ApiResponse[PromoCodeResponse])
async def get_promo_code(
    coupon_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    coupon = await _get_coupon(db, coupon_id)
    return ApiResponse(data=_to_response(coupon))


@router.put("/{coupon_id}", response_model=ApiResponse[PromoCodeResponse])
async def update_promo_code(
    coupon_id: int,
    data: PromoCodeUpdate,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    _validate_payload(data)
    coupon = await _get_coupon(db, coupon_id)
    if data.code:
        await _ensure_code_unique(db, data.code, exclude_id=coupon_id)
    _apply_payload(coupon, data)
    await db.commit()
    await db.refresh(coupon)
    return ApiResponse(data=_to_response(coupon))


@router.delete("/{coupon_id}", response_model=ApiResponse[None])
async def delete_promo_code(
    coupon_id: int,
    db: AsyncSession = Depends(get_db),
    _: User = Depends(_admin),
):
    coupon = await _get_coupon(db, coupon_id)
    usage_count = int(
        (await db.execute(
            select(func.count()).select_from(PromoCodeUsage).where(PromoCodeUsage.promo_code_id == coupon_id)
        )).scalar_one()
        or 0
    )
    if usage_count > 0:
        raise HTTPException(status_code=400, detail="该优惠券已有使用记录，请改为停用以保留财务追溯")
    await db.delete(coupon)
    await db.commit()
    return ApiResponse(data=None)
