"""转盘购票 / 抽奖 / 后台配置（独立业务域，不影响普通点单与抢单池）。

路径前缀：/shangpin/
- zhuanpangoumai   购票下单（写 Czjilu leixing=7 + ZhuanpanJihui）
- zhuanpanlunxun   支付复查
- zhuanpanhuitiao  微信回调
- zhuanpanshibai   关单
- zhuanpanjiangpin 奖品格
- zhuanpanchoujiang 抽奖（后端控奖）
- zhuanpanwode     我的抽奖机会
- adzhuanpan*      后台活动/奖品配置
"""
from __future__ import annotations

import hashlib
import logging
import random
import string
import time
import traceback
from decimal import Decimal

import defusedxml.ElementTree as ET
from django.conf import settings
from django.db import transaction
from django.http import HttpResponse
from django.views import View
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.views import APIView

from jituan.services.club_context import resolve_effective_club_id
from jituan.services.club_user import get_payment_openid
from jituan.services.club_write import resolve_club_id_for_write
from products.czjilu_types import (
    CZJILU_LEIXING_ZHUANPAN,
    ZHUANPAN_ORDER_PREFIX,
    close_unpaid_czjilu,
)
from products.models import (
    Czjilu, Shangpin, ShangpinLeixing, ShangpinZhuanqu,
    ZhuanpanHuodong, ZhuanpanJiangpin, ZhuanpanJihui, ZhuanpanKongjiang,
)
from utils.money import yuan_to_fen

logger = logging.getLogger(__name__)


def _gen_czzp_id():
    timestamp_ms = int(time.time() * 1000)
    timestamp_ns = int(time.time_ns() // 1000) % 1000000
    random_num = random.randint(0, 99)
    return f"{ZHUANPAN_ORDER_PREFIX}{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}"


def _pick_jiangpin(jiangpin_list):
    """按权重抽奖（仅库存未耗尽：kucun==-1 或不限 / kucun>0）。"""
    pool = [j for j in jiangpin_list if int(getattr(j, 'kucun', -1)) != 0]
    if not pool:
        raise ValueError('奖品库存已全部抽完')
    total = sum(max(int(j.quanzhong or 0), 0) for j in pool)
    if total <= 0:
        return pool[0]
    r = random.randint(1, total)
    acc = 0
    for j in pool:
        acc += max(int(j.quanzhong or 0), 0)
        if r <= acc:
            return j
    return pool[-1]


def _consume_jiangpin_stock(jiangpin):
    """普通抽中或控奖命中后扣库存；-1 不限不扣；0 不扣（控奖可强制）。"""
    k = int(getattr(jiangpin, 'kucun', -1))
    if k > 0:
        jiangpin.kucun = k - 1
        jiangpin.save(update_fields=['kucun', 'UpdateTime'])


def _take_kongjiang(club_id, huodong_id, yonghuid):
    """取一条可用控奖并扣减次数；返回奖品对象或 None。"""
    row = (
        ZhuanpanKongjiang.query.select_for_update()
        .filter(
            club_id=club_id,
            huodong_id=huodong_id,
            yonghuid=yonghuid,
            qiyong=True,
            cishu__gt=0,
        )
        .order_by('id')
        .first()
    )
    if not row:
        return None
    prize = ZhuanpanJiangpin.query.filter(id=row.jiangpin_id, huodong_id=huodong_id).first()
    if not prize:
        row.qiyong = False
        row.save(update_fields=['qiyong', 'UpdateTime'])
        return None
    row.cishu = max(int(row.cishu or 0) - 1, 0)
    if row.cishu <= 0:
        row.qiyong = False
    row.save(update_fields=['cishu', 'qiyong', 'UpdateTime'])
    return prize


def _serialize_huodong_row(h, ticket=None):
    if ticket is None:
        ticket = Shangpin.query.filter(id=h.shangpin_id).first()
    return {
        'id': h.id,
        'mingzi': h.mingzi,
        'shangpin_id': h.shangpin_id,
        'qiyong': h.qiyong,
        'beizhu': h.beizhu,
        'biaoqian': (ticket.biaoqian if ticket else '') or h.mingzi,
        'jiage': str(ticket.jiage) if ticket else '0',
        'tupian_url': (ticket.tupian_url if ticket else '') or '',
        'leixing_id': ticket.leixing_id if ticket else None,
        'zhuanqu_id': ticket.zhuanqu_id if ticket else None,
        'shangjia_zhuangtai': bool(ticket.shangjia_zhuangtai) if ticket else False,
    }


def _create_pool_order_from_prize(jihui, jiangpin, request_user):
    """中奖后写普通 Order Status=1 进抢池；谢谢参与则不创建。"""
    if jiangpin.shi_xiexie or not jiangpin.shangpin_id:
        return ''

    from orders.models import Order, PlatformOrderExt
    from jituan.services.club_config import get_commission_rate_object
    from shop.utils import calculate_pingtai_and_dianpu_shouyi

    shangpin = Shangpin.query.filter(id=jiangpin.shangpin_id).first()
    if not shangpin:
        raise ValueError('中奖商品不存在')

    club_id = jihui.club_id
    shangpin_jiage = Decimal(str(shangpin.jiage or 0))
    if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None:
        dashou_fencheng = Decimal(str(shangpin.ewai_dashou_fencheng))
    else:
        dashou_lilu = get_commission_rate_object(club_id, '1')
        rate = Decimal(str(getattr(dashou_lilu, 'Rate', 0) or 0))
        dashou_fencheng = shangpin_jiage * rate

    timestamp = int(time.time())
    timestamp_ns = int(time.time_ns() // 1000) % 1000000
    random_num = random.randint(0, 99)
    dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"

    laoban_yonghuid = getattr(request_user, 'UserUID', '') or jihui.yonghuid
    dingdan = Order.query.create(
        OrderID=dingdanid,
        Status=1,  # 已付待抢
        Amount=shangpin_jiage,
        Platform=1,
        PlayerCommission=dashou_fencheng,
        AssignedID=None,
        ProductID=shangpin.id,
        GrabRequirement=getattr(shangpin, 'yaoqiuleixing', None),
        MembershipID=getattr(shangpin, 'huiyuan_id', '') or '',
        CommissionReq=getattr(shangpin, 'yongjin', 0) or 0,
        Description=getattr(shangpin, 'jieshao', '') or '',
        Remark=(jihui.beizhu or '')[:200],
        Nickname=jihui.nicheng or '',
        ImageURL=getattr(shangpin, 'tupian_url', None) or '',
        User1ID=f"Boss{laoban_yonghuid}",
        ProductTypeID=shangpin.leixing_id,
        ClubID=club_id,
        WechatTransactionID=jihui.czjilu_dingdan_id,
    )

    dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong = \
        calculate_pingtai_and_dianpu_shouyi(
            shangpin_id=shangpin.id,
            dingdan_jiage=shangpin_jiage,
            dashou_fencheng=dashou_fencheng,
        )
    pingtaikuozhan_data = {
        'Order': dingdan,
        'BossID': laoban_yonghuid,
        'PlatformIncome': pingtai_shouyi,
        'ParentShopID': shangji_dianpu_id,
        'ParentBonus': shangji_fenhong if shangji_fenhong else Decimal('0.00'),
        'ShopIncome': dianpu_shouyi if dianpu_id else Decimal('0.00'),
    }
    if dianpu_id:
        pingtaikuozhan_data['ShopID'] = dianpu_id
    PlatformOrderExt.query.create(**pingtaikuozhan_data)

    try:
        from orders.notice_tasks import dingdan_guangbo
        dingdan_guangbo.delay({
            'OrderID': dingdanid,
            'game_type': '',
            'amount': str(shangpin_jiage),
            'order_desc': (getattr(shangpin, 'jieshao', None) or getattr(shangpin, 'biaoqian', '') or '')[:50],
        })
    except Exception as exc:
        logger.warning('转盘中奖广播失败 dingdan=%s: %s', dingdanid, exc)

    return dingdanid


class ZhuanpanGoumai(APIView):
    """POST /shangpin/zhuanpangoumai  转盘购票。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            huodong_id = request.data.get('huodong_id')
            shangpin_id = request.data.get('shangpin_id')
            nicheng = (request.data.get('nicheng') or '').strip()
            beizhu = (request.data.get('Remark') or request.data.get('beizhu') or '').strip()
            pay_method = (request.data.get('pay_method') or 'wechat').strip().lower()

            if not nicheng:
                return Response({'code': 400, 'message': '请填写游戏昵称'}, status=400)

            club_id = resolve_club_id_for_write(request, request.user)
            from jituan.services.alipay_pay import alipay_pay_method_block_reason
            _alipay_block = alipay_pay_method_block_reason(pay_method, club_id)
            if _alipay_block:
                return Response({'code': 400, 'message': _alipay_block}, status=400)
            huodong = None
            if huodong_id:
                huodong = ZhuanpanHuodong.query.filter(
                    id=int(huodong_id), club_id=club_id, qiyong=True
                ).first()
            elif shangpin_id:
                huodong = ZhuanpanHuodong.query.filter(
                    shangpin_id=int(shangpin_id), club_id=club_id, qiyong=True
                ).first()

            if not huodong:
                return Response({'code': 404, 'message': '转盘活动不存在或未启用'}, status=404)

            ticket = Shangpin.query.filter(id=huodong.shangpin_id, club_id=club_id).first()
            if not ticket:
                return Response({'code': 404, 'message': '购票商品不存在'}, status=404)

            jine = Decimal(str(ticket.jiage or 0))
            if jine <= 0:
                return Response({'code': 400, 'message': '购票价格异常'}, status=400)

            prizes = list(ZhuanpanJiangpin.query.filter(huodong_id=huodong.id).order_by('paixu', 'id'))
            if not prizes:
                return Response({'code': 400, 'message': '活动未配置奖品'}, status=400)

            dingdanid = _gen_czzp_id()
            with transaction.atomic():
                Czjilu.query.create(
                    dingdan_id=dingdanid,
                    zhuangtai=9,
                    yonghuid=request.user.UserUID,
                    jine=jine,
                    leixing=CZJILU_LEIXING_ZHUANPAN,
                    shuoming=getattr(settings, 'ZHUANPAN_PAY_DESCRIPTION', '转盘购票'),
                    club_id=club_id,
                )
                ZhuanpanJihui.query.create(
                    club_id=club_id,
                    yonghuid=request.user.UserUID,
                    huodong_id=huodong.id,
                    czjilu_dingdan_id=dingdanid,
                    jine=jine,
                    zhuangtai=9,
                    nicheng=nicheng[:50],
                    beizhu=beizhu[:200],
                )

            if pay_method == 'alipay':
                from jituan.services.alipay_pay import generate_alipay_czjilu_pay_params
                pay_params = generate_alipay_czjilu_pay_params(
                    dingdanid=dingdanid,
                    jine=jine,
                    subject=getattr(settings, 'ZHUANPAN_PAY_DESCRIPTION', '转盘购票'),
                    club_id=club_id,
                )
            else:
                pay_openid, _ = get_payment_openid(request, club_id=club_id)
                if not pay_openid:
                    Czjilu.query.filter(dingdan_id=dingdanid).delete()
                    ZhuanpanJihui.query.filter(czjilu_dingdan_id=dingdanid).delete()
                    return Response({
                        'code': 400,
                        'message': '当前端未绑定微信openid，请重新登录',
                    }, status=400)
                pay_params = self._wechat_pay_params(request, dingdanid, jine, pay_openid, club_id)

            return Response({
                'code': 200,
                'message': 'ok',
                'payParams': pay_params,
                'dingdanid': dingdanid,
                'jihui_id': ZhuanpanJihui.query.filter(czjilu_dingdan_id=dingdanid).first().id,
            })
        except Exception as e:
            logger.error('转盘购票异常: %s', e, exc_info=True)
            return Response({'code': 500, 'message': f'购票失败: {e}'}, status=500)

    def _wechat_pay_params(self, request, dingdanid, jine, openid, club_id):
        from jituan.services.wechat_pay import get_wechat_v2_config
        from jituan.constants import FUBEI_BIZ_CZJILU
        from jituan.services.mini_pay_router import maybe_create_fubei_jsapi_params
        from jituan.services.club_user import get_pay_app_source

        PAY_DESCRIPTION = getattr(settings, 'ZHUANPAN_PAY_DESCRIPTION', '转盘购票')
        NOTIFY_URL = getattr(settings, 'ZHUANPAN_PAY_NOTIFY_URL', '')
        app_source = get_pay_app_source(request)

        fb_params = None
        if app_source not in ('official', 'h5', 'oa'):
            fb_params = maybe_create_fubei_jsapi_params(
                club_id=club_id,
                out_trade_no=dingdanid,
                amount_yuan=jine,
                body=PAY_DESCRIPTION,
                openid=openid,
                notify_url=NOTIFY_URL,
                biz_type=FUBEI_BIZ_CZJILU,
            )
        if fb_params is not None:
            return fb_params

        pay_cfg = get_wechat_v2_config(club_id, app_source=app_source)
        APPID = pay_cfg['appid']
        MCHID = pay_cfg['mch_id']
        KEY = pay_cfg['key']
        nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
        total_fee = yuan_to_fen(jine)

        import requests as http_requests
        params = {
            'appid': APPID,
            'mch_id': MCHID,
            'nonce_str': nonce_str,
            'body': PAY_DESCRIPTION,
            'out_trade_no': dingdanid,
            'total_fee': str(total_fee),
            'spbill_create_ip': request.META.get('REMOTE_ADDR', '127.0.0.1'),
            'notify_url': NOTIFY_URL,
            'trade_type': 'JSAPI',
            'openid': openid,
        }
        sign_str = '&'.join(f'{k}={params[k]}' for k in sorted(params) if params[k]) + f'&key={KEY}'
        params['sign'] = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
        xml_body = '<xml>' + ''.join(f'<{k}><![CDATA[{v}]]></{k}>' for k, v in params.items()) + '</xml>'
        resp = http_requests.post('https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_body.encode('utf-8'), timeout=15)
        root = ET.fromstring(resp.content)
        if root.findtext('return_code') != 'SUCCESS' or root.findtext('result_code') != 'SUCCESS':
            raise Exception(root.findtext('err_code_des') or root.findtext('return_msg') or '统一下单失败')
        prepay_id = root.findtext('prepay_id')
        ts = str(int(time.time()))
        pay_nonce = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
        pkg = f'prepay_id={prepay_id}'
        pay_sign_src = f'appId={APPID}&nonceStr={pay_nonce}&package={pkg}&signType=MD5&timeStamp={ts}&key={KEY}'
        return {
            'timeStamp': ts,
            'nonceStr': pay_nonce,
            'package': pkg,
            'signType': 'MD5',
            'paySign': hashlib.md5(pay_sign_src.encode('utf-8')).hexdigest().upper(),
        }


class ZhuanpanLunxun(APIView):
    """POST /shangpin/zhuanpanlunxun"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        dingdanid = request.data.get('dingdanid')
        if not dingdanid:
            return Response({'code': 400, 'message': '缺少 dingdanid'}, status=400)
        try:
            from jituan.services.czjilu_fulfill import confirm_czjilu_paid, CzjiluFulfillError
            result = confirm_czjilu_paid(
                dingdanid, request.user.UserUID, expected_leixing=CZJILU_LEIXING_ZHUANPAN
            )
            jihui = ZhuanpanJihui.query.filter(czjilu_dingdan_id=dingdanid).first()
            return Response({
                'code': 200,
                'message': 'ok',
                'data': {
                    **result,
                    'jihui_id': jihui.id if jihui else None,
                    'jihui_zhuangtai': jihui.zhuangtai if jihui else None,
                },
            })
        except Exception as e:
            from jituan.services.czjilu_fulfill import CzjiluFulfillError
            if isinstance(e, CzjiluFulfillError):
                return Response({'code': 400, 'message': str(e)}, status=400)
            logger.error('转盘轮询异常: %s', e, exc_info=True)
            return Response({'code': 500, 'message': str(e)}, status=500)


class ZhuanpanShibai(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        dingdanid = request.data.get('dingdanid')
        if not dingdanid:
            return Response({'code': 400, 'message': '缺少 dingdanid'}, status=400)
        order = Czjilu.query.filter(
            dingdan_id=dingdanid, yonghuid=request.user.UserUID, leixing=CZJILU_LEIXING_ZHUANPAN
        ).first()
        if not order:
            return Response({'code': 404, 'message': '订单不存在'}, status=404)
        close_unpaid_czjilu(order, reason='用户取消转盘支付')
        jihui = ZhuanpanJihui.query.filter(czjilu_dingdan_id=dingdanid).first()
        if jihui and int(jihui.zhuangtai) == 9:
            jihui.zhuangtai = 3
            jihui.save(update_fields=['zhuangtai', 'UpdateTime'])
        return Response({'code': 200, 'message': 'ok'})


class ZhuanpanHuitiao(View):
    """微信转盘购票回调 /shangpin/zhuanpanhuitiao"""

    def post(self, request):
        try:
            xml_data = request.body
            root = ET.fromstring(xml_data)
            if root.findtext('return_code') != 'SUCCESS':
                return self._wx('FAIL', 'return_code')
            out_trade_no = root.findtext('out_trade_no') or ''
            total_fee = root.findtext('total_fee')
            paid = None
            if total_fee:
                paid = Decimal(total_fee) / Decimal('100')
            from jituan.services.czjilu_fulfill import fulfill_zhuanpan
            fulfill_zhuanpan(out_trade_no, paid_amount_yuan=paid)
            return self._wx('SUCCESS', 'OK')
        except Exception as e:
            logger.error('转盘微信回调异常: %s', e, exc_info=True)
            return self._wx('FAIL', 'error')

    def _wx(self, code, msg):
        return HttpResponse(
            f'<xml><return_code><![CDATA[{code}]]></return_code>'
            f'<return_msg><![CDATA[{msg}]]></return_msg></xml>',
            content_type='application/xml',
        )


class ZhuanpanJiangpinList(APIView):
    """POST /shangpin/zhuanpanjiangpin  拉奖品格（可匿名，按 club）。"""
    permission_classes = [AllowAny]

    def post(self, request):
        club_id = resolve_effective_club_id(request)
        huodong_id = request.data.get('huodong_id')
        shangpin_id = request.data.get('shangpin_id')
        q = ZhuanpanHuodong.query.filter(club_id=club_id, qiyong=True)
        if huodong_id:
            q = q.filter(id=int(huodong_id))
        elif shangpin_id:
            q = q.filter(shangpin_id=int(shangpin_id))
        huodong = q.first()
        if not huodong:
            return Response({'code': 404, 'message': '无可用转盘活动', 'data': None}, status=404)
        prizes = list(
            ZhuanpanJiangpin.query.filter(huodong_id=huodong.id).order_by('paixu', 'id')
        )
        return Response({
            'code': 200,
            'message': 'ok',
            'data': {
                'huodong': {
                    'id': huodong.id,
                    'mingzi': huodong.mingzi,
                    'shangpin_id': huodong.shangpin_id,
                },
                'jiangpin': [
                    {
                        'id': p.id,
                        'mingzi': p.mingzi,
                        'tupian_url': p.tupian_url,
                        'paixu': p.paixu,
                        'shi_xiexie': p.shi_xiexie,
                        'shangpin_id': p.shangpin_id,
                    }
                    for p in prizes
                ],
            },
        })


class ZhuanpanChoujiang(APIView):
    """POST /shangpin/zhuanpanchoujiang  执行抽奖（后端控奖）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        jihui_id = request.data.get('jihui_id')
        if not jihui_id:
            return Response({'code': 400, 'message': '缺少 jihui_id'}, status=400)
        try:
            with transaction.atomic():
                jihui = ZhuanpanJihui.query.select_for_update().filter(
                    id=int(jihui_id), yonghuid=request.user.UserUID
                ).first()
                if not jihui:
                    return Response({'code': 404, 'message': '抽奖机会不存在'}, status=404)
                if int(jihui.zhuangtai) != 1:
                    return Response({
                        'code': 400,
                        'message': '机会未支付或已抽过',
                        'data': {'zhuangtai': jihui.zhuangtai},
                    }, status=400)

                prizes = list(
                    ZhuanpanJiangpin.query.select_for_update()
                    .filter(huodong_id=jihui.huodong_id)
                    .order_by('paixu', 'id')
                )
                if not prizes:
                    return Response({'code': 400, 'message': '奖品未配置'}, status=400)

                # 1) 控奖优先  2) 权重+库存
                win = _take_kongjiang(jihui.club_id, jihui.huodong_id, jihui.yonghuid)
                if win is None:
                    win = _pick_jiangpin(prizes)
                _consume_jiangpin_stock(win)

                dingdan_id = ''
                if not win.shi_xiexie and win.shangpin_id:
                    dingdan_id = _create_pool_order_from_prize(jihui, win, request.user)

                jihui.zhuangtai = 2
                jihui.zhongjiang_jiangpin_id = win.id
                jihui.zhongjiang_shangpin_id = win.shangpin_id
                jihui.dingdan_id = dingdan_id or ''
                jihui.save(update_fields=[
                    'zhuangtai', 'zhongjiang_jiangpin_id', 'zhongjiang_shangpin_id',
                    'dingdan_id', 'UpdateTime',
                ])

            return Response({
                'code': 200,
                'message': 'ok',
                'data': {
                    'jihui_id': jihui.id,
                    'jiangpin_id': win.id,
                    'mingzi': win.mingzi,
                    'tupian_url': win.tupian_url,
                    'paixu': win.paixu,
                    'shi_xiexie': win.shi_xiexie,
                    'dingdan_id': dingdan_id,
                },
            })
        except Exception as e:
            logger.error('抽奖异常: %s\n%s', e, traceback.format_exc())
            return Response({'code': 500, 'message': str(e)}, status=500)


class ZhuanpanWode(APIView):
    """POST /shangpin/zhuanpanwode"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        club_id = resolve_effective_club_id(request)
        qs = ZhuanpanJihui.query.filter(
            yonghuid=request.user.UserUID, club_id=club_id
        ).exclude(zhuangtai=9).order_by('-id')[:50]
        return Response({
            'code': 200,
            'message': 'ok',
            'data': [
                {
                    'id': j.id,
                    'huodong_id': j.huodong_id,
                    'zhuangtai': j.zhuangtai,
                    'jine': str(j.jine),
                    'nicheng': j.nicheng,
                    'zhongjiang_jiangpin_id': j.zhongjiang_jiangpin_id,
                    'dingdan_id': j.dingdan_id,
                    'czjilu_dingdan_id': j.czjilu_dingdan_id,
                    'CreateTime': j.CreateTime.isoformat() if j.CreateTime else '',
                }
                for j in qs
            ],
        })


class AdZhuanpanHuodongView(APIView):
    """后台：转盘商品(=活动) list / create_product / save / toggle / meta。

    运营不再手填购票商品编号：创建时直接填名称/价格/类型，自动建 Shangpin+Huodong。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        action = (request.data.get('action') or 'list').strip()
        club_id = resolve_effective_club_id(request)

        if action == 'meta':
            types = list(
                ShangpinLeixing.query.order_by('id').values('id', 'jieshao')
            )
            zones = list(
                ShangpinZhuanqu.query.filter(club_id=club_id)
                .order_by('-paixu', 'id')
                .values('id', 'mingzi', 'leixing_id')
            )
            # 履约商品：本俱乐部非转盘购票商品
            goods = list(
                Shangpin.query.filter(club_id=club_id, shi_zhuanpan=False)
                .order_by('-paixu', '-id')
                .values('id', 'biaoqian', 'jiage', 'tupian_url', 'leixing_id')[:500]
            )
            for g in goods:
                g['jiage'] = str(g.get('jiage') or 0)
            return Response({
                'code': 0,
                'data': {'leixing': types, 'zhuanqu': zones, 'goods': goods},
            })

        if action == 'list':
            rows = list(ZhuanpanHuodong.query.filter(club_id=club_id).order_by('-id'))
            sp_ids = [h.shangpin_id for h in rows]
            tickets = {
                s.id: s
                for s in Shangpin.query.filter(id__in=sp_ids, club_id=club_id)
            } if sp_ids else {}
            return Response({
                'code': 0,
                'data': [_serialize_huodong_row(h, tickets.get(h.shangpin_id)) for h in rows],
            })

        if action == 'create_product':
            biaoqian = (request.data.get('biaoqian') or request.data.get('mingzi') or '').strip()
            mingzi = (request.data.get('mingzi') or biaoqian or '幸运转盘').strip()
            try:
                jiage = Decimal(str(request.data.get('jiage') or 0))
            except Exception:
                return Response({'code': 1, 'msg': '价格格式错误'})
            if not biaoqian:
                return Response({'code': 1, 'msg': '请填写转盘商品名称'})
            if jiage <= 0:
                return Response({'code': 1, 'msg': '价格必须大于 0'})
            leixing_id = request.data.get('leixing_id')
            if not leixing_id:
                return Response({'code': 1, 'msg': '请选择商品类型'})
            try:
                leixing_id = int(leixing_id)
            except (TypeError, ValueError):
                return Response({'code': 1, 'msg': '商品类型无效'})
            zhuanqu_id = request.data.get('zhuanqu_id') or None
            if zhuanqu_id in ('', None):
                zhuanqu_id = None
            else:
                zhuanqu_id = int(zhuanqu_id)
            tupian_url = (request.data.get('tupian_url') or '').strip()
            beizhu = (request.data.get('beizhu') or '').strip()
            qiyong = bool(request.data.get('qiyong', True))

            with transaction.atomic():
                ticket = Shangpin.query.create(
                    biaoqian=biaoqian[:45],
                    jiage=jiage,
                    kucun=99999,
                    leixing_id=leixing_id,
                    zhuanqu_id=zhuanqu_id,
                    jieshao=beizhu or '转盘购票商品',
                    tupian_url=tupian_url,
                    club_id=club_id,
                    shi_zhuanpan=True,
                    shangjia_zhuangtai=True,
                    fengjin_zhuangtai=True,
                    shenhezhuangtai=1,
                    paixu=0,
                )
                h = ZhuanpanHuodong.query.create(
                    club_id=club_id,
                    mingzi=mingzi[:64],
                    shangpin_id=ticket.id,
                    beizhu=beizhu,
                    qiyong=qiyong,
                )
            return Response({'code': 0, 'data': _serialize_huodong_row(h, ticket)})

        if action == 'save':
            hid = request.data.get('id')
            if not hid:
                return Response({'code': 1, 'msg': '缺少 id，新建请用 create_product'})
            h = ZhuanpanHuodong.query.filter(id=int(hid), club_id=club_id).first()
            if not h:
                return Response({'code': 1, 'msg': '转盘不存在'})
            ticket = Shangpin.query.filter(id=h.shangpin_id, club_id=club_id).first()
            mingzi = (request.data.get('mingzi') or h.mingzi).strip()
            biaoqian = (request.data.get('biaoqian') or (ticket.biaoqian if ticket else mingzi) or mingzi).strip()
            beizhu = request.data.get('beizhu')
            if beizhu is None:
                beizhu = h.beizhu
            else:
                beizhu = str(beizhu).strip()
            qiyong = bool(request.data.get('qiyong', h.qiyong))
            tupian_url = request.data.get('tupian_url')
            jiage_raw = request.data.get('jiage')

            h.mingzi = mingzi[:64]
            h.beizhu = beizhu
            h.qiyong = qiyong
            h.save()
            if ticket:
                ticket.biaoqian = biaoqian[:45]
                ticket.shi_zhuanpan = True
                if tupian_url is not None:
                    ticket.tupian_url = str(tupian_url).strip()
                if jiage_raw is not None and str(jiage_raw).strip() != '':
                    try:
                        jv = Decimal(str(jiage_raw))
                        if jv > 0:
                            ticket.jiage = jv
                    except Exception:
                        return Response({'code': 1, 'msg': '价格格式错误'})
                ticket.save()
            return Response({'code': 0, 'data': _serialize_huodong_row(h, ticket)})

        if action == 'toggle':
            hid = int(request.data.get('id') or 0)
            h = ZhuanpanHuodong.query.filter(id=hid, club_id=club_id).first()
            if not h:
                return Response({'code': 1, 'msg': '不存在'})
            h.qiyong = not h.qiyong
            h.save(update_fields=['qiyong', 'UpdateTime'])
            ticket = Shangpin.query.filter(id=h.shangpin_id, club_id=club_id).first()
            if ticket:
                ticket.shangjia_zhuangtai = h.qiyong
                ticket.save(update_fields=['shangjia_zhuangtai', 'UpdateTime'])
            return Response({'code': 0, 'data': {'qiyong': h.qiyong}})

        return Response({'code': 1, 'msg': '未知 action'})


class AdZhuanpanJiangpinView(APIView):
    """后台：奖品 list/save/delete（含库存；履约商品用下拉 id，不手填语义）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        action = (request.data.get('action') or 'list').strip()
        club_id = resolve_effective_club_id(request)
        huodong_id = int(request.data.get('huodong_id') or 0)
        huodong = ZhuanpanHuodong.query.filter(id=huodong_id, club_id=club_id).first()
        if not huodong:
            return Response({'code': 1, 'msg': '转盘不存在或不属于当前俱乐部'})

        if action == 'list':
            rows = list(ZhuanpanJiangpin.query.filter(huodong_id=huodong_id).order_by('paixu', 'id'))
            fulfill_ids = [p.shangpin_id for p in rows if p.shangpin_id]
            name_map = {}
            if fulfill_ids:
                name_map = {
                    s.id: s.biaoqian
                    for s in Shangpin.query.filter(id__in=fulfill_ids, club_id=club_id)
                }
            return Response({
                'code': 0,
                'data': [
                    {
                        'id': p.id,
                        'mingzi': p.mingzi,
                        'tupian_url': p.tupian_url,
                        'shangpin_id': p.shangpin_id,
                        'shangpin_biaoqian': name_map.get(p.shangpin_id, '') if p.shangpin_id else '',
                        'quanzhong': p.quanzhong,
                        'kucun': int(getattr(p, 'kucun', -1)),
                        'paixu': p.paixu,
                        'shi_xiexie': p.shi_xiexie,
                    }
                    for p in rows
                ],
            })
        if action == 'save':
            pid = request.data.get('id')
            kucun_raw = request.data.get('kucun', -1)
            try:
                kucun = int(kucun_raw)
            except (TypeError, ValueError):
                kucun = -1
            data = dict(
                mingzi=(request.data.get('mingzi') or '').strip() or '奖品',
                tupian_url=(request.data.get('tupian_url') or '').strip(),
                shangpin_id=request.data.get('shangpin_id') or None,
                quanzhong=int(request.data.get('quanzhong') or 1),
                kucun=kucun,
                paixu=int(request.data.get('paixu') or 0),
                shi_xiexie=bool(request.data.get('shi_xiexie', False)),
            )
            if data['shangpin_id'] in ('', None):
                data['shangpin_id'] = None
            else:
                data['shangpin_id'] = int(data['shangpin_id'])
                # 履约商品必须属于本俱乐部且不能是转盘购票商品
                ful = Shangpin.query.filter(id=data['shangpin_id'], club_id=club_id).first()
                if not ful:
                    return Response({'code': 1, 'msg': '履约商品不存在'})
                if getattr(ful, 'shi_zhuanpan', False):
                    return Response({'code': 1, 'msg': '不能把转盘购票商品设为履约商品'})
            if data['shi_xiexie']:
                data['shangpin_id'] = None
            if pid:
                p = ZhuanpanJiangpin.query.filter(id=int(pid), huodong_id=huodong_id).first()
                if not p:
                    return Response({'code': 1, 'msg': '奖品不存在'})
                for k, v in data.items():
                    setattr(p, k, v)
                p.save()
            else:
                p = ZhuanpanJiangpin.query.create(huodong_id=huodong_id, **data)
            return Response({'code': 0, 'data': {'id': p.id}})
        if action == 'delete':
            pid = int(request.data.get('id') or 0)
            ZhuanpanJiangpin.query.filter(id=pid, huodong_id=huodong_id).delete()
            ZhuanpanKongjiang.query.filter(jiangpin_id=pid, huodong_id=huodong_id).delete()
            return Response({'code': 0})
        return Response({'code': 1, 'msg': '未知 action'})


class AdZhuanpanKongjiangView(APIView):
    """后台控奖：指定用户必中某奖若干次。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        action = (request.data.get('action') or 'list').strip()
        club_id = resolve_effective_club_id(request)
        huodong_id = int(request.data.get('huodong_id') or 0)
        huodong = ZhuanpanHuodong.query.filter(id=huodong_id, club_id=club_id).first()
        if not huodong:
            return Response({'code': 1, 'msg': '转盘不存在或不属于当前俱乐部'})

        if action == 'list':
            rows = list(
                ZhuanpanKongjiang.query.filter(huodong_id=huodong_id, club_id=club_id)
                .order_by('-id')[:200]
            )
            jp_ids = [r.jiangpin_id for r in rows]
            jp_map = {
                p.id: p.mingzi
                for p in ZhuanpanJiangpin.query.filter(id__in=jp_ids, huodong_id=huodong_id)
            } if jp_ids else {}
            return Response({
                'code': 0,
                'data': [
                    {
                        'id': r.id,
                        'yonghuid': r.yonghuid,
                        'jiangpin_id': r.jiangpin_id,
                        'jiangpin_mingzi': jp_map.get(r.jiangpin_id, ''),
                        'cishu': r.cishu,
                        'qiyong': r.qiyong,
                        'beizhu': r.beizhu,
                    }
                    for r in rows
                ],
            })
        if action == 'save':
            yonghuid = str(request.data.get('yonghuid') or '').strip()
            jiangpin_id = int(request.data.get('jiangpin_id') or 0)
            cishu = int(request.data.get('cishu') or 1)
            beizhu = (request.data.get('beizhu') or '').strip()
            qiyong = bool(request.data.get('qiyong', True))
            if not yonghuid:
                return Response({'code': 1, 'msg': '请填写用户 UID'})
            if cishu < 1:
                return Response({'code': 1, 'msg': '次数至少为 1'})
            prize = ZhuanpanJiangpin.query.filter(id=jiangpin_id, huodong_id=huodong_id).first()
            if not prize:
                return Response({'code': 1, 'msg': '奖品不存在'})
            kid = request.data.get('id')
            if kid:
                row = ZhuanpanKongjiang.query.filter(
                    id=int(kid), huodong_id=huodong_id, club_id=club_id
                ).first()
                if not row:
                    return Response({'code': 1, 'msg': '控奖记录不存在'})
                row.yonghuid = yonghuid
                row.jiangpin_id = jiangpin_id
                row.cishu = cishu
                row.beizhu = beizhu
                row.qiyong = qiyong
                row.save()
            else:
                row = ZhuanpanKongjiang.query.create(
                    club_id=club_id,
                    huodong_id=huodong_id,
                    yonghuid=yonghuid,
                    jiangpin_id=jiangpin_id,
                    cishu=cishu,
                    beizhu=beizhu,
                    qiyong=qiyong,
                )
            return Response({'code': 0, 'data': {'id': row.id}})
        if action == 'delete':
            kid = int(request.data.get('id') or 0)
            ZhuanpanKongjiang.query.filter(
                id=kid, huodong_id=huodong_id, club_id=club_id
            ).delete()
            return Response({'code': 0})
        return Response({'code': 1, 'msg': '未知 action'})
