import hmac
import threading
import traceback
import uuid
import hashlib
import re
import xmltodict
import time
import logging
import requests
import os
import secrets
import random
import string
from calendar import monthrange
from decimal import Decimal
from datetime import date, datetime, timedelta
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.db import models, transaction, IntegrityError
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from gvsdsdk.fluent import db, func, FQ
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from django.contrib.auth.hashers import make_password
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser

# 工具类导入
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from backend.utils import (
    update_dashou_daily_by_action,
    update_shangjia_daily
)
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
from jituan.services.catalog_scope import block_non_group_catalog_scope
from jituan.services.club_penalty import (
    filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
    forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
)
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
from shop.utils import update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from orders.notice_tasks import dingdan_guangbo
from ..utils import (
    verify_kefu_permission,
    PERMISSION_TO_SHENFEN,
    SHENFEN_PROFILE_MAP,
    has_perm_code,
    has_fadan_view_permission,
    has_merchant_order_permission,
    check_fadan_permission,
    pick_order_id,
    pick_penalty_create_params,
    write_xiugai_log,
    XIUGAI_LEIXING_DASHOU,
    XIUGAI_LEIXING_GUANSHI,
    XIUGAI_LEIXING_SHANGJIA,
    XIUGAI_LEIXING_ZUZHANG,
    XIUGAI_LEIXING_LABEL,
)

# models 集中导入
## gvsdsdk RBAC 模型（使用 PascalCase 字段名，匹配实际数据库表结构）
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
## backend
from backend.models import WithdrawalDailyStats

## users
from users.models import (
    UserGuanshi, UserBoss, UserZuzhang,
    UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan,
    DashouZengsongHuiyuan,
)
from users.business_models import User
from jituan.constants import CLUB_ID_DEFAULT

## orders
from orders.models import (
    CommissionRate, PlayerDeliveryImage, PenaltyRecord,
    OrderPlayerHistory, Order, RefundRecord,
    MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
)

## shop
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi

## products
from products.models import (
    Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
    DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
)

## config
from config.models import (
    WithdrawConfig, ShangjiaLianjie, AccountPermission,
    TixianQuotaDefault,
    DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
)

## rank
from rank.models import (
    KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
    YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
)

# 序列化器
from ..serializers import PopupPageSerializer

# 全局常量、日志对象
logger = logging.getLogger('houtai')
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']

# 与前端约定的聊天权限码（原位于 shops.py，按功能组归类到客服模块）
CHAT_PERM_CODES = ['abca1', 'baac2', 'cabc3', 'cb3a2', 'bcaa4']

class KefuGetDashouListView(APIView):
    """
    客服获取打手列表接口（仅打手类型）
    需要拥有以下任一权限：001aa,001bb,001cc,001dd,001ee,001ff,001gg
    请求：POST /jituan/houtai/kefuhqdslb
    参数：
        username: 客服手机号（用于权限校验）
        keyword: 用户ID / 昵称 / 手机号（可选）
        zhanghaozhuangtai: 1正常 / 0封禁（可选）
        has_member: 1有有效会员 / 0无（可选）
        huiyuan_id: 限定有效会员含该类型（可选）
        yajin_op + yajin_value: gte/lte/eq + 数值（可选）
        zaixianzhuangtai / zhuangtai: 在线 / 空闲接单（可选）
        page / page_size: 分页
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = (request.data.get('username') or '').strip()
        keyword = (request.data.get('keyword') or '').strip()
        huiyuan_id = (request.data.get('huiyuan_id') or '').strip()
        yajin_op = (request.data.get('yajin_op') or '').strip().lower()
        yajin_value_raw = request.data.get('yajin_value', None)
        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '分页参数错误'})

        if not username:
            return Response({'code': 401, 'msg': '认证失败'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        required_perms = ('001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg')
        if not has_perm_code(permissions, *required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看打手列表'})

        dashou_qs = filter_user_related_by_club(
            UserDashou.query.select_related('user').all(), request,
        )

        if keyword:
            dashou_qs = dashou_qs.filter(
                Q(user__UserUID__icontains=keyword) |
                Q(nicheng__icontains=keyword) |
                Q(user__Phone__icontains=keyword)
            )

        zhanghao_raw = request.data.get('zhanghaozhuangtai', None)
        if zhanghao_raw is not None and zhanghao_raw != '':
            try:
                zhanghao_val = int(zhanghao_raw)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '账号状态格式错误'})
            if zhanghao_val not in (0, 1):
                return Response({'code': 400, 'msg': '账号状态值无效'})
            dashou_qs = dashou_qs.filter(zhanghaozhuangtai=zhanghao_val)

        zaixian_raw = request.data.get('zaixianzhuangtai', None)
        if zaixian_raw is not None and zaixian_raw != '':
            try:
                zaixian_val = int(zaixian_raw)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '在线状态格式错误'})
            if zaixian_val not in (0, 1):
                return Response({'code': 400, 'msg': '在线状态值无效'})
            dashou_qs = dashou_qs.filter(zaixianzhuangtai=zaixian_val)

        zhuangtai_raw = request.data.get('zhuangtai', None)
        if zhuangtai_raw is not None and zhuangtai_raw != '':
            try:
                zhuangtai_val = int(zhuangtai_raw)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '接单状态格式错误'})
            if zhuangtai_val not in (0, 1):
                return Response({'code': 400, 'msg': '接单状态值无效'})
            dashou_qs = dashou_qs.filter(zhuangtai=zhuangtai_val)

        now = timezone.now()
        # 有效会员：与打手详情 / clumber 一致——daoqi_time > now（未过期）。
        # 含购买表 Huiyuangoumai + 赠送表 DashouZengsongHuiyuan。
        # 必须用 Exists(OuterRef)，禁止 FluentQuery.values() 丢进 __in：
        # values() 返回的是字典迭代器，UserUID__in=[{'yonghu_id':...}] 永远匹配不到。
        bought_any = Huiyuangoumai.objects.filter(
            yonghu_id=OuterRef('user__UserUID'),
            daoqi_time__gt=now,
        )
        gift_any = DashouZengsongHuiyuan.objects.filter(
            yonghu_id=OuterRef('user__UserUID'),
            daoqi_time__gt=now,
        )
        dashou_qs = dashou_qs.annotate(
            _any_hy=Exists(bought_any),
            _any_gift=Exists(gift_any),
        )

        if huiyuan_id:
            bought_type = Huiyuangoumai.objects.filter(
                yonghu_id=OuterRef('user__UserUID'),
                daoqi_time__gt=now,
                huiyuan_id=huiyuan_id,
            )
            gift_type = DashouZengsongHuiyuan.objects.filter(
                yonghu_id=OuterRef('user__UserUID'),
                daoqi_time__gt=now,
                huiyuan_id=huiyuan_id,
            )
            dashou_qs = dashou_qs.annotate(
                _type_hy=Exists(bought_type),
                _type_gift=Exists(gift_type),
            )

        has_member_raw = request.data.get('has_member', None)
        if has_member_raw is not None and has_member_raw != '':
            try:
                has_member_val = int(has_member_raw)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '会员筛选格式错误'})
            if has_member_val == 1:
                if huiyuan_id:
                    dashou_qs = dashou_qs.filter(Q(_type_hy=True) | Q(_type_gift=True))
                else:
                    dashou_qs = dashou_qs.filter(Q(_any_hy=True) | Q(_any_gift=True))
            elif has_member_val == 0:
                dashou_qs = dashou_qs.filter(_any_hy=False, _any_gift=False)
            else:
                return Response({'code': 400, 'msg': '会员筛选值无效'})
        elif huiyuan_id:
            dashou_qs = dashou_qs.filter(Q(_type_hy=True) | Q(_type_gift=True))

        # 押金≥阈值人数：在应用押金比较筛之前统计（相对其它已选条件）
        yajin_gte_count = None
        yajin_value = None
        if yajin_value_raw is not None and yajin_value_raw != '':
            try:
                yajin_value = Decimal(str(yajin_value_raw))
            except Exception:
                return Response({'code': 400, 'msg': '押金数值格式错误'})
            yajin_gte_count = dashou_qs.filter(yajin__gte=yajin_value).count()
            op = yajin_op if yajin_op in ('gte', 'lte', 'eq') else 'gte'
            if op == 'gte':
                dashou_qs = dashou_qs.filter(yajin__gte=yajin_value)
            elif op == 'lte':
                dashou_qs = dashou_qs.filter(yajin__lte=yajin_value)
            else:
                dashou_qs = dashou_qs.filter(yajin=yajin_value)

        total_dashou = dashou_qs.count()
        _status_agg = dashou_qs.aggregate(
            normal_count=Count('id', filter=Q(zhanghaozhuangtai=1)),
            banned_count=Count('id', filter=Q(zhanghaozhuangtai=0)),
        )
        valid_dashou_count = dashou_qs.filter(Q(_any_hy=True) | Q(_any_gift=True)).count()
        no_member_count = max(total_dashou - valid_dashou_count, 0)

        offset = (page - 1) * page_size
        dashou_list = list(dashou_qs.order_by('-CreateTime')[offset:offset + page_size])
        page_uids = [d.user.UserUID for d in dashou_list if getattr(d, 'user', None)]

        hy_by_uid = {}
        if page_uids:
            name_ids = set()
            bought_rows = list(Huiyuangoumai.objects.filter(
                yonghu_id__in=page_uids,
                daoqi_time__gt=now,
            ).values('yonghu_id', 'huiyuan_id', 'jieshao'))
            gift_rows = list(DashouZengsongHuiyuan.objects.filter(
                yonghu_id__in=page_uids,
                daoqi_time__gt=now,
            ).values('yonghu_id', 'huiyuan_id'))
            for row in bought_rows:
                name_ids.add(row['huiyuan_id'])
            for row in gift_rows:
                name_ids.add(row['huiyuan_id'])
            name_map = {
                h['huiyuan_id']: (h['jieshao'] or '')
                for h in Huiyuan.objects.filter(huiyuan_id__in=name_ids).values('huiyuan_id', 'jieshao')
            }
            seen_pair = set()
            for row in bought_rows:
                uid = row['yonghu_id']
                hid = row['huiyuan_id']
                key = (uid, hid)
                if key in seen_pair:
                    continue
                seen_pair.add(key)
                name = (row.get('jieshao') or '').strip() or name_map.get(hid, '') or hid
                hy_by_uid.setdefault(uid, []).append({
                    'huiyuan_id': hid,
                    'name': name,
                })
            for row in gift_rows:
                uid = row['yonghu_id']
                hid = row['huiyuan_id']
                key = (uid, hid)
                if key in seen_pair:
                    continue
                seen_pair.add(key)
                name = name_map.get(hid, '') or hid
                hy_by_uid.setdefault(uid, []).append({
                    'huiyuan_id': hid,
                    'name': name,
                    'is_gift': True,
                })

        result = []
        for dashou in dashou_list:
            user = dashou.user
            uid = user.UserUID
            result.append({
                'yonghuid': uid,
                'avatar': user.Avatar or '',
                'phone': user.Phone or '',
                'club_id': getattr(user, 'ClubID', None) or '',
                'zaixianzhuangtai': dashou.zaixianzhuangtai,
                'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
                'nicheng': dashou.nicheng or '',
                'zhuangtai': dashou.zhuangtai,
                'yajin': str(dashou.yajin if dashou.yajin is not None else '0'),
                'huiyuan_list': hy_by_uid.get(uid, []),
            })

        has_more = (offset + len(dashou_list)) < total_dashou
        huiyuan_options = list(
            Huiyuan.query.all().order_by('huiyuan_id').values('huiyuan_id', 'jieshao')
        )

        meta = list_response_meta(request)
        return Response({
            'code': 0,
            'club_id': meta['club_id'],
            'scope': meta['scope'],
            'data': {
                'list': result,
                'has_more': has_more,
                'total': total_dashou,
                'valid_dashou_count': valid_dashou_count,
                'no_member_count': no_member_count,
                'normal_count': _status_agg['normal_count'] or 0,
                'banned_count': _status_agg['banned_count'] or 0,
                'yajin_gte_count': yajin_gte_count,
                'huiyuan_options': huiyuan_options,
            }
        })



class KefuGetDashouDetailView(APIView):
    """
    客服获取打手详情接口
    请求：POST /yonghu/kefuhqdsxq
    参数：{
        "username": "客服手机号",
        "uid": "打手ID (yonghuid)"
    }
    认证：JWT
    返回：code=0 + 打手信息 + 会员列表 + 所有会员类型 + 客服权限列表
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        # 1. 获取参数
        username = request.data.get('phone', '').strip()
        uid = request.data.get('uid', '').strip()

        if not username or not uid:
            return Response({'code': 400, 'msg': '缺少参数'})

        # 2. 公共权限校验（验证客服身份，获取权限列表）
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 3. 检查是否拥有打手管理相关权限（至少一个）
        required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看打手详情'})

        # 4. 查询打手主表和扩展表
        try:
            user_main = User.query.select_related('DashouProfile').get(
                UserUID=uid
            )
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})

        denied = forbid_if_user_out_of_scope(request, user_main)
        if denied:
            return denied

        try:
            dashou = user_main.DashouProfile
        except ObjectDoesNotExist:
            # 用户存在但未注册打手身份，返回基本信息
            user_info = {
                'yonghuid': user_main.UserUID,
                'avatar': user_main.Avatar or '',
                'zaixianzhuangtai': 0,
                'zhanghaozhuangtai': 0,
                'zhuangtai': 0,
                'nicheng': '',
                'chenghao': '',
                'dianhua': '',
                'wechat': '',
                'phone': user_main.Phone or '',
                'CreateTime': user_main.UserCreateTime,
                'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
                'ip': user_main.IP or '',
                'jiedanzongliang': 0,
                'chengjiaozongliang': 0,
                'tuikuanliang': 0,
                'jinrijiedan': 0,
                'yue': 0.00,
                'zonge': 0.00,
                'jifen': 0,
                'yajin': 0.00,
                'jinrishouyi': 0.00,
                'jinyueshouyi': 0.00,
                'yaoqingren': '',
                'jieshao': '',
            }
            member_list = []
            # 仍然需要返回 all_members 和 permissions
            all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))
            return Response({
                'code': 0,
                'data': {
                    'user_info': user_info,
                    'member_list': member_list,
                    'gift_member_list': [],
                    'all_members': all_members,
                    'permissions': permissions,
                }
            })

        # 5. 构建打手信息
        user_info = {
            'yonghuid': user_main.UserUID,
            'avatar': user_main.Avatar or '',
            'zaixianzhuangtai': dashou.zaixianzhuangtai,
            'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
            'zhuangtai': dashou.zhuangtai,
            'fengjin_yuanyin': getattr(dashou, 'fengjin_yuanyin', '') or '',
            'nicheng': dashou.nicheng or '',
            'chenghao': dashou.chenghao or '',
            'dianhua': dashou.dianhua or '',
            'wechat': dashou.wechat or '',
            'phone': user_main.Phone or '',
            'CreateTime': user_main.UserCreateTime,
            'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
            'ip': user_main.IP or '',
            'jiedanzongliang': dashou.jiedanzongliang,
            'chengjiaozongliang': dashou.chengjiaozongliang,
            'tuikuanliang': dashou.tuikuanliang,
            'jinrijiedan': dashou.jinrijiedan,
            'yue': float(dashou.yue) if dashou.yue else 0.00,
            'dongjie_yue': float(getattr(dashou, 'dongjie_yue', 0) or 0),
            'risk_fine_rate': float(getattr(dashou, 'risk_fine_rate', 0) or 0),
            'risk_complaint_rate': float(getattr(dashou, 'risk_complaint_rate', 0) or 0),
            'zonge': float(dashou.zonge) if dashou.zonge else 0.00,
            'jifen': dashou.jifen,
            'yajin': float(dashou.yajin) if dashou.yajin else 0.00,
            'jinrishouyi': float(dashou.jinrishouyi) if dashou.jinrishouyi else 0.00,
            'jinyueshouyi': float(dashou.jinyueshouyi) if dashou.jinyueshouyi else 0.00,
            'yaoqingren': dashou.yaoqingren or '',
            'jieshao': dashou.jieshao or '',
        }

        # 6. 查询打手已拥有的会员（未过期）
        member_records = Huiyuangoumai.query.filter(
            yonghu_id=uid
        ).order_by('-CreateTime')
        # 批量预取会员信息
        _huiyuan_ids = [r.huiyuan_id for r in member_records]
        _huiyuan_map = {
            h.huiyuan_id: h.jieshao
            for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao')
        }
        member_list = []
        for record in member_records:
            # 检查是否过期
            if hasattr(record, 'jiance_shifou_daoqi'):
                is_expired = record.jiance_shifou_daoqi()
            else:
                is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True

            if not is_expired:
                huiyuan_name = _huiyuan_map.get(record.huiyuan_id, '未知会员')
                member_list.append({
                    'id': record.id,
                    'huiyuan_name': huiyuan_name,
                    'daoqi_time': record.daoqi_time,
                    'is_active': record.huiyuan_zhuangtai == 1,
                    'huiyuan_id': record.huiyuan_id,  # 用于删除操作
                })

        # 7. 查询所有会员类型（用于前端添加会员）
        all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))

        from jituan.services.gift_huiyuan import list_gift_huiyuan_for_admin
        gift_member_list = list_gift_huiyuan_for_admin(uid)

        return Response({
            'code': 0,
            'data': {
                'user_info': user_info,
                'member_list': member_list,
                'gift_member_list': gift_member_list,
                'all_members': all_members,
                'permissions': permissions,  # 返回当前客服的权限列表
            }
        })



class KefuUpdateDashouView(APIView):
    """
    客服修改打手信息接口
    支持操作：
    - jian_yue / jia_yue           : 减/加打手余额 (权限001aa/001bb)
    - jian_yajin / jia_yajin       : 减/加打手押金 (权限001aa/001bb)
    - jian_jifen / jia_jifen       : 减/加打手积分 (权限001cc/001dd)
    - gai_zhanghaozhuangtai        : 修改打手账号状态（封禁/解封） (权限001ee)
    - gai_zaixianzhuangtai         : 修改打手在线状态 (权限001ee)
    - gai_zhuangtai                : 修改打手接单状态（空闲/忙碌） (权限001ee)
    - jia_huiyuan / jian_huiyuan   : 加/减会员 (权限001ff/001gg)
    - gai_chenghao                 : 修改打手称号（升级/降级金牌） (权限001hh)
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取前端参数
        username = request.data.get('username', '').strip()
        dashou_id = request.data.get('dashou_id', '').strip()
        caozuo = request.data.get('caozuo', '').strip()
        value = request.data.get('value')          # 金额、积分、天数、称号等
        huiyuan_id = request.data.get('huiyuan_id', '').strip()

        if not username or not dashou_id or not caozuo:
            return Response({'code': 400, 'msg': '参数不完整'})

        # 2. 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 3. 查询打手
        try:
            dashou_user = User.query.get(UserUID=dashou_id)
            dashou_profile = dashou_user.DashouProfile
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})
        except UserDashou.DoesNotExist:
            return Response({'code': 404, 'msg': '打手扩展信息缺失'})

        with transaction.atomic():
            try:
                before = {
                    'yue': dashou_profile.yue,
                    'yajin': dashou_profile.yajin,
                    'jifen': dashou_profile.jifen,
                    'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai,
                    'zaixianzhuangtai': dashou_profile.zaixianzhuangtai,
                    'zhuangtai': dashou_profile.zhuangtai,
                    'chenghao': dashou_profile.chenghao,
                }

                # ---------- 余额操作 ----------
                if caozuo == 'jian_yue':
                    if '001aa' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手余额'})
                    amount = Decimal(str(value))
                    if dashou_profile.yue < amount:
                        return Response({'code': 400, 'msg': '余额不足'})
                    dashou_profile.yue -= amount
                    dashou_profile.save()
                    after = dashou_profile.yue
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        xiugaitijiao=after,
                        xiugaitijiaoq=before['yue'],
                        qitashuoming=(
                            f'【后台】减少打手可提现余额：{before["yue"]}元 → {after}元，'
                            f'减少{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减余额成功', 'data': {'new_yue': str(after)}})

                elif caozuo == 'jia_yue':
                    if '001bb' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手余额'})
                    from jituan.services.group_finance_gate import deny_balance_add_exec
                    from jituan.services.club_context import resolve_club_id_from_request
                    deny = deny_balance_add_exec(
                        request, permissions=permissions, target_user=dashou_user,
                    )
                    if deny:
                        return deny
                    amount = Decimal(str(value))
                    dashou_profile.yue += amount
                    dashou_profile.save()
                    after = dashou_profile.yue
                    club_id = resolve_club_id_from_request(request) or ''
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        xiugaitijiao=after,
                        xiugaitijiaoq=before['yue'],
                        qitashuoming=(
                            f'【后台】增加打手可提现余额：{before["yue"]}元 → {after}元，'
                            f'增加{amount}元，打手ID={dashou_id}，俱乐部={club_id}，'
                            f'操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加余额成功', 'data': {'new_yue': str(after)}})

                # ---------- 押金操作 ----------
                elif caozuo == 'jian_yajin':
                    if '001aa' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手押金'})
                    amount = Decimal(str(value))
                    if dashou_profile.yajin < amount:
                        return Response({'code': 400, 'msg': '押金不足'})
                    dashou_profile.yajin -= amount
                    dashou_profile.save()
                    after = dashou_profile.yajin
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        yajin=after,
                        yyajin=before['yajin'],
                        qitashuoming=(
                            f'【后台】减少打手押金：{before["yajin"]}元 → {after}元，'
                            f'减少{amount}元，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减押金成功', 'data': {'new_yajin': str(after)}})

                elif caozuo == 'jia_yajin':
                    if '001bb' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手押金'})
                    from jituan.services.group_finance_gate import deny_balance_add_exec
                    from jituan.services.club_context import resolve_club_id_from_request
                    deny = deny_balance_add_exec(
                        request, permissions=permissions, target_user=dashou_user,
                    )
                    if deny:
                        return deny
                    amount = Decimal(str(value))
                    dashou_profile.yajin += amount
                    dashou_profile.save()
                    after = dashou_profile.yajin
                    club_id = resolve_club_id_from_request(request) or ''
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        yajin=after,
                        yyajin=before['yajin'],
                        qitashuoming=(
                            f'【后台】增加打手押金：{before["yajin"]}元 → {after}元，'
                            f'增加{amount}元，打手ID={dashou_id}，俱乐部={club_id}，'
                            f'操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加押金成功', 'data': {'new_yajin': str(after)}})

                # ---------- 积分操作 ----------
                elif caozuo == 'jian_jifen':
                    if '001cc' not in permissions:
                        return Response({'code': 403, 'msg': '无权限减打手积分'})
                    jifen = int(value)
                    if dashou_profile.jifen < jifen:
                        return Response({'code': 400, 'msg': '积分不足'})
                    dashou_profile.jifen -= jifen
                    dashou_profile.save()
                    after = dashou_profile.jifen
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        jifen=after,
                        yjifen=before['jifen'],
                        qitashuoming=(
                            f'【后台】减少打手积分：{before["jifen"]} → {after}，'
                            f'减少{jifen}分，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减积分成功', 'data': {'new_jifen': after}})

                elif caozuo == 'jia_jifen':
                    if '001dd' not in permissions:
                        return Response({'code': 403, 'msg': '无权限加打手积分'})
                    jifen = int(value)
                    dashou_profile.jifen += jifen
                    dashou_profile.save()
                    after = dashou_profile.jifen
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        jifen=after,
                        yjifen=before['jifen'],
                        qitashuoming=(
                            f'【后台】增加打手积分：{before["jifen"]} → {after}，'
                            f'增加{jifen}分，打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加积分成功', 'data': {'new_jifen': after}})

                # ---------- 状态修改 ----------
                elif caozuo == 'gai_zhanghaozhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手账号状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=封禁,1=正常）'})
                    yuanyin = (request.data.get('yuanyin') or request.data.get('fengjin_yuanyin') or '').strip()
                    dashou_profile.zhanghaozhuangtai = new_status
                    if new_status == 0:
                        dashou_profile.fengjin_yuanyin = yuanyin[:200]
                    else:
                        dashou_profile.fengjin_yuanyin = ''
                    dashou_profile.save(update_fields=['zhanghaozhuangtai', 'fengjin_yuanyin'])
                    status_label = '正常' if new_status == 1 else '封禁'
                    old_label = '正常' if before['zhanghaozhuangtai'] == 1 else '封禁'
                    reason_part = f'，原因={yuanyin}' if (new_status == 0 and yuanyin) else ''
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手账号状态：{old_label} → {status_label}{reason_part}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({
                        'code': 0,
                        'msg': '修改账号状态成功',
                        'data': {
                            'new_zhanghaozhuangtai': new_status,
                            'fengjin_yuanyin': dashou_profile.fengjin_yuanyin or '',
                        },
                    })

                elif caozuo == 'gai_zaixianzhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手在线状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=离线,1=在线）'})
                    dashou_profile.zaixianzhuangtai = new_status
                    dashou_profile.save()
                    status_label = '在线' if new_status == 1 else '离线'
                    old_label = '在线' if before['zaixianzhuangtai'] == 1 else '离线'
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手在线状态：{old_label} → {status_label}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改在线状态成功', 'data': {'new_zaixianzhuangtai': new_status}})

                elif caozuo == 'gai_zhuangtai':
                    if '001ee' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手接单状态'})
                    new_status = int(value)
                    if new_status not in [0, 1]:
                        return Response({'code': 400, 'msg': '状态值无效（0=忙碌,1=空闲）'})
                    dashou_profile.zhuangtai = new_status
                    dashou_profile.save()
                    status_label = '空闲' if new_status == 1 else '忙碌'
                    old_label = '空闲' if before['zhuangtai'] == 1 else '忙碌'
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手接单状态：{old_label} → {status_label}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改接单状态成功', 'data': {'new_zhuangtai': new_status}})

                # ---------- 会员操作 ----------
                elif caozuo == 'jia_huiyuan':
                    if '001ff' not in permissions and '000001' not in permissions:
                        return Response({'code': 403, 'msg': '无权限给打手加会员'})
                    if not huiyuan_id:
                        return Response({'code': 400, 'msg': '缺少会员ID'})
                    try:
                        huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
                    except Huiyuan.DoesNotExist:
                        return Response({'code': 404, 'msg': '会员类型不存在'})
                    from jituan.services.club_user import get_user_club_id
                    from jituan.services.member_recharge import extend_member_entitlement_admin, get_club_huiyuan_row
                    player_club = get_user_club_id(dashou_user)
                    row = get_club_huiyuan_row(player_club, huiyuan_id, require_enabled=False)
                    days = int(value) if value else int(row.formal_days or 30) if row else 30
                    record, err = extend_member_entitlement_admin(
                        dashou_id, huiyuan_id, days, player_club,
                    )
                    if err:
                        return Response({'code': 400, 'msg': err})
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        huiyuants=days,
                        huiyuan_id=huiyuan_id,
                        qitashuoming=(
                            f'【后台】给打手添加正式会员：会员ID={huiyuan_id}（{huiyuan.jieshao}），'
                            f'增加{days}天，到期时间={record.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}（无支付单、无分红）'
                        ),
                    )
                    return Response({'code': 0, 'msg': '加会员成功', 'data': {'new_daoqi': record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S')}})

                elif caozuo == 'jian_huiyuan':
                    if '001gg' not in permissions:
                        return Response({'code': 403, 'msg': '无权限给打手减会员'})
                    if not huiyuan_id:
                        return Response({'code': 400, 'msg': '缺少会员ID'})
                    from jituan.services.member_recharge import revoke_member_entitlement_admin
                    removed, err = revoke_member_entitlement_admin(dashou_id, huiyuan_id)
                    if err:
                        return Response({'code': 404, 'msg': err})
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        huiyuan_id=huiyuan_id,
                        qitashuoming=(
                            f'【后台】移除打手会员：会员ID={huiyuan_id}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '减会员成功'})

                # ---------- 称号修改（金牌打手） ----------
                elif caozuo == 'gai_chenghao':
                    if '001hh' not in permissions:
                        return Response({'code': 403, 'msg': '无权限修改打手称号'})
                    new_chenghao = str(value).strip()
                    if not new_chenghao:
                        return Response({'code': 400, 'msg': '称号不能为空'})
                    # 可限制合法值，这里只记录日志
                    dashou_profile.chenghao = new_chenghao
                    dashou_profile.save()
                    write_xiugai_log(
                        yonghuid=dashou_id,
                        xiugaiid=kefu.user.Phone,
                        leixing=XIUGAI_LEIXING_DASHOU,
                        qitashuoming=(
                            f'【后台】修改打手称号：{before["chenghao"] or "无"} → {new_chenghao}，'
                            f'打手ID={dashou_id}，操作人={kefu.user.Phone}'
                        ),
                    )
                    return Response({'code': 0, 'msg': '修改称号成功', 'data': {'new_chenghao': new_chenghao}})

                else:
                    return Response({'code': 400, 'msg': '未知操作类型'})

            except Exception as e:
                logger.error(f"修改打手信息异常: {str(e)}", exc_info=True)
                return Response({'code': 500, 'msg': '服务器内部错误'})






class KefuGetShangjiaListView(APIView):
    """
    客服/管理员获取商家列表接口
    需要拥有以下任一权限：1100a, 1100b
    请求：POST /houtai/hqsjgllb
    参数：
        username: 客服手机号（用于权限校验，必填）
        keyword: 搜索关键词（可选，搜索商家用户ID或昵称）
        balance_op: 余额比较符，gte(>=) / lte(<=)（可选）
        balance_value: 余额数值（可选，需与balance_op同时存在）
        order_count_op: 派单量比较符，gte(>=) / lte(<=)（可选）
        order_count_value: 派单量数值（可选，需与order_count_op同时存在）
        status: 商家状态，1=正常，2=禁用（可选）
        page: 页码，从1开始，默认1
        page_size: 每页数量，默认20
    返回：
        code: 0 成功，其他失败
        data: {
            list: [商家对象],
            total: int,          // 总商家数
        }
    """
    permission_classes = []   # 不依赖全局认证，手动校验
    parser_classes = [JSONParser]

    def post(self, request):
        # 1. 获取参数
        username = request.data.get('username', '').strip()
        keyword = request.data.get('keyword', '').strip()
        balance_op = request.data.get('balance_op', '').strip().lower()
        balance_value_str = request.data.get('balance_value')
        order_count_op = request.data.get('order_count_op', '').strip().lower()
        order_count_value_str = request.data.get('order_count_value')
        status_str = request.data.get('status')

        try:
            page = int(request.data.get('page', 1))
            page_size = int(request.data.get('page_size', 20))
            if page < 1 or page_size < 1:
                raise ValueError
        except ValueError:
            return Response({'code': 400, 'msg': '分页参数错误'})

        if not username:
            return Response({'code': 401, 'msg': '认证失败，缺少username'})

        # 2. 公共权限校验（复用现有函数 verify_kefu_permission）
        # 假设 verify_kefu_permission 定义在某个公共模块，这里引入

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions   # 直接返回错误响应

        # 3. 检查商家管理权限（1100a 或 1100b）
        required_perms = {'1100a', '1100b'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看商家列表'})

        # 4. 构建商家查询集（商家扩展表 + 关联用户主表）
        shangjia_qs = filter_user_related_by_club(
            UserShangjia.query.select_related('user').all(), request,
        )

        # 关键词筛选（用户ID或昵称）
        if keyword:
            shangjia_qs = shangjia_qs.filter(
                Q(user__UserUID__icontains=keyword) |
                Q(nicheng__icontains=keyword)
            )

        # 余额筛选
        if balance_op and balance_value_str is not None and balance_value_str != '':
            try:
                balance_value = Decimal(str(balance_value_str))
                if balance_op == 'gte':
                    shangjia_qs = shangjia_qs.filter(yue__gte=balance_value)
                elif balance_op == 'lte':
                    shangjia_qs = shangjia_qs.filter(yue__lte=balance_value)
                else:
                    return Response({'code': 400, 'msg': '余额比较符只支持gte或lte'})
            except Exception:
                return Response({'code': 400, 'msg': '余额数值格式错误'})

        # 派单总量筛选（fabu字段）
        if order_count_op and order_count_value_str is not None and order_count_value_str != '':
            try:
                order_count_value = int(order_count_value_str)
                if order_count_op == 'gte':
                    shangjia_qs = shangjia_qs.filter(fabu__gte=order_count_value)
                elif order_count_op == 'lte':
                    shangjia_qs = shangjia_qs.filter(fabu__lte=order_count_value)
                else:
                    return Response({'code': 400, 'msg': '派单量比较符只支持gte或lte'})
            except Exception:
                return Response({'code': 400, 'msg': '派单量数值必须为整数'})

        # 商家状态筛选
        if status_str is not None and status_str != '':
            try:
                status_val = int(status_str)
                if status_val not in [1, 2]:
                    return Response({'code': 400, 'msg': '商家状态值无效，应为1或2'})
                shangjia_qs = shangjia_qs.filter(zhuangtai=status_val)
            except ValueError:
                return Response({'code': 400, 'msg': '商家状态格式错误'})

        # 5. 统计总商家数（应用筛选后）
        _agg = shangjia_qs.aggregate(
            total=Count('id'),
            valid=Count('id', filter=Q(zhuangtai=1)),
        )
        total = _agg['total']
        valid_merchant_count = _agg['valid']

        # 6. 分页
        offset = (page - 1) * page_size
        shangjia_list = shangjia_qs.order_by('-CreateTime')[offset:offset + page_size]

        # 7. 构建返回数据
        result = []
        for shangjia in shangjia_list:
            user = shangjia.user
            result.append({
                'yonghuid': user.UserUID,
                'avatar': user.Avatar or '',
                'nicheng': shangjia.nicheng or '',
                'yue': str(shangjia.yue),          # 转换为字符串，避免前端精度问题
                'fabu': shangjia.fabu,
                'chengjiao': shangjia.chengjiao,
                'zhuangtai': shangjia.zhuangtai,    # 1=正常,2=禁用
            })

        meta = list_response_meta(request)
        return Response({
            'code': 0,
            'club_id': meta['club_id'],
            'scope': meta['scope'],
            'data': {
                'list': result,
                'total': total,
                'valid_merchant_count': valid_merchant_count,
            }
        })






# yonghu/views.py 中添加

class KefuGetShangjiaDetailView(APIView):
    """
    客服/管理员获取商家详情接口
    需要权限：1100a 或 1100b
    请求：POST /houtai/hthqsjxq
    参数：
        username: 客服手机号（用于权限校验，必填）
        yonghuid: 商家用户ID（必填）
    返回：
        code: 0 成功
        data: 商家所有字段（含主表头像等）
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()

        if not username:
            return Response({'code': 401, 'msg': '认证失败，缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少商家ID'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        required_perms = {'1100a', '1100b'}
        if not any(p in permissions for p in required_perms):
            return Response({'code': 403, 'msg': '您没有权限查看商家详情'})

        # 查询商家
        try:
            shangjia = UserShangjia.query.select_related('user').get(user__UserUID=yonghuid)
        except UserShangjia.DoesNotExist:
            return Response({'code': 404, 'msg': '商家不存在'})

        denied = forbid_if_user_out_of_scope(request, shangjia.user)
        if denied:
            return denied

        user = shangjia.user

        data = {
            'yonghuid': user.UserUID,
            'avatar': user.Avatar or '',
            'nicheng': shangjia.nicheng,
            'zhuangtai': shangjia.zhuangtai,        # 1正常，2禁用
            'dianhua': shangjia.dianhua,
            'wechat': shangjia.wechat,
            'yue': str(shangjia.yue),
            'fabu': shangjia.fabu,
            'chengjiao': shangjia.chengjiao,
            'tuikuan': shangjia.tuikuan,
            'jinridingdan': shangjia.jinridingdan,
            'jinriliushui': str(shangjia.jinriliushui),
            'jinyuedingdan': shangjia.jinyuedingdan,
            'jinyueliushui': str(shangjia.jinyueliushui),
            'is_youzhi_shangjia': bool(shangjia.is_youzhi_shangjia),
            'is_teyue_shangjia': bool(getattr(shangjia, 'is_teyue_shangjia', False)),
            'CreateTime': shangjia.CreateTime.isoformat() if shangjia.CreateTime else None,
            'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
        }

        return Response({'code': 0, 'data': data})







class KefuUpdateShangjiaView(APIView):
    """
    客服/管理员修改商家信息（余额、状态）
    POST /houtai/xgsjxx
    参数：
        username, yonghuid, action
        action可选：add_balance, sub_balance, ban, unban
        amount（add_balance/sub_balance时必填）
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = request.data.get('yonghuid', '').strip()
        action = request.data.get('action', '').strip().lower()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '缺少商家ID'})
        if action not in ('add_balance', 'sub_balance', 'ban', 'unban', 'set_youzhi', 'set_teyue'):
            return Response({'code': 400, 'msg': 'action参数无效'})

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions

        # 操作权限校验
        if action in ('add_balance', 'ban', 'unban', 'set_youzhi', 'set_teyue') and '1100a' not in permissions:
            return Response({'code': 403, 'msg': '无权限执行此操作（需要1100a）'})
        if action == 'sub_balance' and '1100b' not in permissions:
            return Response({'code': 403, 'msg': '无权限减少商家余额（需要1100b）'})

        # 使用事务，select_for_update 必须在事务内
        with transaction.atomic():
            try:
                shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=yonghuid)
            except UserShangjia.DoesNotExist:
                return Response({'code': 404, 'msg': '商家不存在'})

            # 增加余额
            if action == 'add_balance':
                from jituan.services.group_finance_gate import deny_balance_add_exec
                from jituan.services.club_context import resolve_club_id_from_request
                deny = deny_balance_add_exec(
                    request, permissions=permissions, target_user=shangjia.user,
                )
                if deny:
                    return deny
                amount_str = request.data.get('amount')
                try:
                    amount = Decimal(str(amount_str))
                    if amount <= 0 or amount > 1000000:
                        return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
                except:
                    return Response({'code': 400, 'msg': '金额格式错误'})
                old_yue = shangjia.yue
                new_yue = shangjia.yue + amount
                if new_yue > 999999999.99:
                    return Response({'code': 400, 'msg': '增加后余额超出上限'})
                shangjia.yue = new_yue
                shangjia.save(update_fields=['yue'])
                club_id = resolve_club_id_from_request(request) or ''
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    shangjiayue=new_yue,
                    shangjiayueq=old_yue,
                    qitashuoming=(
                        f'【后台】增加商家余额：{old_yue}元 → {new_yue}元，增加{amount}元，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，俱乐部={club_id}，'
                        f'操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '余额增加成功', 'data': {'new_balance': str(shangjia.yue)}})

            # 减少余额
            if action == 'sub_balance':
                amount_str = request.data.get('amount')
                try:
                    amount = Decimal(str(amount_str))
                    if amount <= 0 or amount > 1000000:
                        return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
                except:
                    return Response({'code': 400, 'msg': '金额格式错误'})
                if shangjia.yue < amount:
                    return Response({'code': 400, 'msg': '余额不足'})
                old_yue = shangjia.yue
                shangjia.yue = shangjia.yue - amount
                shangjia.save(update_fields=['yue'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    shangjiayue=shangjia.yue,
                    shangjiayueq=old_yue,
                    qitashuoming=(
                        f'【后台】减少商家余额：{old_yue}元 → {shangjia.yue}元，减少{amount}元，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '余额减少成功', 'data': {'new_balance': str(shangjia.yue)}})

            # 封禁
            if action == 'ban':
                if shangjia.zhuangtai == 2:
                    return Response({'code': 400, 'msg': '商家已是封禁状态'})
                shangjia.zhuangtai = 2
                shangjia.save(update_fields=['zhuangtai'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】封禁商家账号：正常 → 禁用，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '商家已封禁', 'data': {'zhuangtai': 2}})

            # 解封
            if action == 'unban':
                if shangjia.zhuangtai == 1:
                    return Response({'code': 400, 'msg': '商家已是正常状态'})
                shangjia.zhuangtai = 1
                shangjia.save(update_fields=['zhuangtai'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】解封商家账号：禁用 → 正常，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({'code': 0, 'msg': '商家已解封', 'data': {'zhuangtai': 1}})

            if action == 'set_youzhi':
                if '1100a' not in permissions:
                    return Response({'code': 403, 'msg': '无权限设置优质商家（需要1100a）'})
                is_youzhi = bool(request.data.get('is_youzhi_shangjia', False))
                shangjia.is_youzhi_shangjia = is_youzhi
                shangjia.save(update_fields=['is_youzhi_shangjia'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】设置优质商家：{is_youzhi}，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({
                    'code': 0,
                    'msg': '优质商家设置成功',
                    'data': {'is_youzhi_shangjia': is_youzhi},
                })

            if action == 'set_teyue':
                if '1100a' not in permissions:
                    return Response({'code': 403, 'msg': '无权限设置特约商家（需要1100a）'})
                is_teyue = bool(request.data.get('is_teyue_shangjia', False))
                shangjia.is_teyue_shangjia = is_teyue
                shangjia.save(update_fields=['is_teyue_shangjia'])
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】设置特约商家（可更换打手）：{is_teyue}，'
                        f'商家ID={yonghuid}，昵称={shangjia.nicheng}，操作人={kefu.user.Phone}'
                    ),
                )
                return Response({
                    'code': 0,
                    'msg': '特约商家设置成功',
                    'data': {'is_teyue_shangjia': is_teyue},
                })

        return Response({'code': 400, 'msg': '未知操作'})


class KefuAddShangjiaView(APIView):
    """
    后台按用户ID开通商家（分俱乐部，禁止前端自助注册场景使用）
    POST /houtai/tjsj  或  /jituan/houtai/tjsj
    参数：username, yonghuid（用户业务ID）, nicheng（可选）
    权限：1100a
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        yonghuid = str(request.data.get('yonghuid', '')).strip()
        nicheng_in = (request.data.get('nicheng') or '').strip()

        if not username:
            return Response({'code': 401, 'msg': '缺少username'})
        if not yonghuid:
            return Response({'code': 400, 'msg': '请填写用户ID'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '1100a' not in permissions:
            return Response({'code': 403, 'msg': '无权限添加商家（需要1100a）'})

        try:
            user = User.query.get(UserUID=yonghuid)
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '用户不存在，请确认用户ID'})

        denied = forbid_if_user_out_of_scope(request, user)
        if denied:
            return denied

        if not getattr(user, 'IsActive', True):
            return Response({'code': 400, 'msg': '该账号已被封禁，无法开通商家'})

        try:
            dashou = user.DashouProfile
            if int(getattr(dashou, 'zhanghaozhuangtai', 1) or 0) != 1:
                return Response({'code': 400, 'msg': '该账号已被封禁，无法开通商家'})
        except UserDashou.DoesNotExist:
            pass
        except Exception:
            pass

        if UserShangjia.query.filter(user=user).exists():
            return Response({'code': 400, 'msg': '该用户已是商家，无需重复添加'})

        from merchant_ops.services.authz import assert_no_active_staff_when_merchant_register, StaffAuthError
        try:
            assert_no_active_staff_when_merchant_register(user)
        except StaffAuthError as e:
            return Response({'code': 403, 'msg': getattr(e, 'msg', None) or str(e)})
        except Exception:
            pass

        boss_nickname = '普通商家'
        try:
            boss = user.BossProfile
            if boss.nickname and boss.nickname.strip():
                boss_nickname = boss.nickname.strip()
        except UserBoss.DoesNotExist:
            pass

        shangjia_nicheng = nicheng_in or boss_nickname
        if UserShangjia.query.filter(nicheng=shangjia_nicheng).exists():
            shangjia_nicheng = f"{shangjia_nicheng}{random.randint(1000, 9999)}"

        try:
            with transaction.atomic():
                shop = UserShangjia.query.create(
                    user=user,
                    nicheng=shangjia_nicheng,
                    zhuangtai=1,
                    yue=0.00,
                    fabu=0,
                    tuikuan=0,
                    chengjiao=0,
                    jinridingdan=0,
                    jinriliushui=0.00,
                    jinyuedingdan=0,
                    jinyueliushui=0.00,
                    dianhua='',
                    wechat='',
                )
                write_xiugai_log(
                    yonghuid=yonghuid,
                    xiugaiid=kefu.user.Phone,
                    leixing=XIUGAI_LEIXING_SHANGJIA,
                    qitashuoming=(
                        f'【后台】添加商家：用户ID={yonghuid}，昵称={shop.nicheng}，'
                        f'操作人={kefu.user.Phone}'
                    ),
                )
        except Exception as e:
            logger.exception('后台添加商家失败')
            return Response({'code': 500, 'msg': f'添加失败：{e}'})

        return Response({
            'code': 0,
            'msg': '商家添加成功',
            'data': {
                'yonghuid': yonghuid,
                'nicheng': shop.nicheng,
                'zhuangtai': shop.zhuangtai,
            },
        })


class KefuChatPermissionsView(APIView):
    """
    获取客服聊天权限及GoEasy配置
    POST /houtai/kfhqltqx
    Body: { "phone": "客服手机号" }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 从前端获取传递的账号，用于防越权校验
        username_from_frontend = request.data.get('phone', None)

        # 2. 调用公共验证方法。
        #    返回 (kefu_obj, permissions) 或 (None, Response)
        kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)

        # 3. 如果第一个返回值为 None，说明验证失败，直接返回第二个值（错误Response）
        if kefu_obj is None:
            return permissions

        # ---------- 验证通过，permissions 已经是权限码列表 ----------
        # 4. 检查是否拥有至少一个聊天权限
        # 必须用 has_perm_code：系统超管是 _AllPermissions，
        # 「any(p in CHAT_PERM_CODES for p in permissions)」会漏掉超管（列表里往往只有 000001）
        has_chat_perm = has_perm_code(permissions, *CHAT_PERM_CODES)

        # 5. 若有聊天权限，获取 GoEasy AppKey
        goeasy_appkey = ''
        if has_chat_perm:
            goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '')

        # 6. 返回数据
        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'permissions': permissions,
                'goeasy_appkey': goeasy_appkey
            }
        })


class KefuImUserClubFilterView(APIView):
    """
    批量校验 GoEasy 客户 ID 所属俱乐部（供客服待接入/已接入列表前端过滤）。
    POST /houtai/kfimssclub
    Body: { "phone": "客服手机号", "im_ids": ["Boss1234567", "Ds7654321"] }
    返回 allowed_ids：当前后台俱乐部可见的 IM ID（集团汇总则全部可解析的都返回）
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    _IM_UID_RE = re.compile(
        r'^(Boss|Ds|Sj|Gs|Zz|Guanshi|Zuzhang|Kaoheguan)(\d+)$',
        re.IGNORECASE,
    )

    def post(self, request):
        phone = (request.data.get('phone') or request.data.get('username') or '').strip()
        kefu_obj, permissions = verify_kefu_permission(request, phone or None)
        if kefu_obj is None:
            return permissions
        if not has_perm_code(permissions, *CHAT_PERM_CODES):
            return Response({'code': 403, 'msg': '无聊天权限', 'data': None})

        raw_ids = request.data.get('im_ids') or request.data.get('ids') or []
        if not isinstance(raw_ids, list):
            return Response({'code': 400, 'msg': 'im_ids 必须为数组', 'data': None})

        # 去重、截断，避免一次打太多
        im_ids = []
        seen = set()
        for x in raw_ids[:200]:
            s = str(x or '').strip()
            if s and s not in seen:
                seen.add(s)
                im_ids.append(s)

        uid_to_ims = {}
        for im_id in im_ids:
            m = self._IM_UID_RE.match(im_id)
            if not m:
                continue
            uid = m.group(2)
            uid_to_ims.setdefault(uid, []).append(im_id)

        club_map = {}
        if uid_to_ims:
            for u in User.query.filter(UserUID__in=list(uid_to_ims.keys())).only('UserUID', 'ClubID'):
                cid = (getattr(u, 'ClubID', None) or '').strip() or CLUB_ID_DEFAULT
                for im_id in uid_to_ims.get(u.UserUID, []):
                    club_map[im_id] = cid

        from jituan.constants import DATA_SCOPE_ALL
        from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope

        scope = resolve_club_scope(request)
        if scope == DATA_SCOPE_ALL:
            allowed_ids = [i for i in im_ids if i in club_map]
        else:
            current = (resolve_club_id_from_request(request) or '').strip()
            # 严格按用户归属俱乐部过滤；ClubID 空的用户归入默认俱乐部
            allowed_ids = [
                i for i in im_ids
                if (club_map.get(i) or '').strip() == current
            ]

        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'allowed_ids': allowed_ids,
                'club_map': club_map,
                'current_club_id': (
                    None if scope == DATA_SCOPE_ALL
                    else (resolve_club_id_from_request(request) or '').strip()
                ),
                'scope': 'all' if scope == DATA_SCOPE_ALL else 'single',
            },
        })


class DashouGiftHuiyuanSaveView(APIView):
    """
    赠送会员 / 改期（免费接单资格，写入 dashou_zengsong_huiyuan）
    POST /houtai/dszsonghy
    Body: {
      phone, dashou_id, huiyuan_id,
      daoqi_time: 'YYYY-MM-DD' 或 'YYYY-MM-DD HH:MM:SS',
      id?: 已有记录ID（改期时建议传）
    }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        from jituan.services.gift_huiyuan import (
            has_gift_huiyuan_permission,
            parse_gift_daoqi_time,
            upsert_gift_huiyuan,
            update_gift_huiyuan_daoqi,
        )
        from products.models import Huiyuan as HuiyuanModel

        phone = (request.data.get('phone') or request.data.get('username') or '').strip()
        dashou_id = (request.data.get('dashou_id') or request.data.get('uid') or '').strip()
        huiyuan_id = (request.data.get('huiyuan_id') or '').strip()
        record_id = request.data.get('id')
        raw_daoqi = request.data.get('daoqi_time')

        kefu, permissions = verify_kefu_permission(request, phone or None)
        if kefu is None:
            return permissions
        if not has_gift_huiyuan_permission(permissions):
            return Response({'code': 403, 'msg': '无赠送会员权限（需打手基础管理或加积分权限）'})

        if not dashou_id:
            return Response({'code': 400, 'msg': '缺少打手ID'})
        daoqi_time, err = parse_gift_daoqi_time(raw_daoqi)
        if err:
            return Response({'code': 400, 'msg': err})

        try:
            dashou_user = User.query.get(UserUID=dashou_id)
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})
        denied = forbid_if_user_out_of_scope(request, dashou_user)
        if denied:
            return denied
        try:
            dashou_user.DashouProfile
        except ObjectDoesNotExist:
            return Response({'code': 404, 'msg': '该用户不是打手'})

        song_ren = getattr(kefu.user, 'Phone', None) or phone
        operator = song_ren

        # 有记录ID：只改期
        if record_id not in (None, ''):
            try:
                rid = int(record_id)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '无效的记录ID'})
            rec, err = update_gift_huiyuan_daoqi(
                record_id=rid,
                yonghu_id=dashou_id,
                daoqi_time=daoqi_time,
                song_ren=song_ren,
            )
            if err:
                return Response({'code': 400, 'msg': err})
            try:
                hy_name = HuiyuanModel.query.get(huiyuan_id=rec.huiyuan_id).jieshao
            except HuiyuanModel.DoesNotExist:
                hy_name = rec.huiyuan_id
            write_xiugai_log(
                yonghuid=dashou_id,
                xiugaiid=operator,
                leixing=XIUGAI_LEIXING_DASHOU,
                huiyuan_id=rec.huiyuan_id,
                qitashuoming=(
                    f'【后台】修改赠送会员到期时间：会员ID={rec.huiyuan_id}（{hy_name}），'
                    f'新到期={rec.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}，'
                    f'打手ID={dashou_id}，操作人={operator}'
                ),
            )
            return Response({
                'code': 0,
                'msg': '改期成功',
                'data': {
                    'id': rec.id,
                    'huiyuan_id': rec.huiyuan_id,
                    'daoqi_time': rec.daoqi_time.strftime('%Y-%m-%d %H:%M:%S'),
                },
            })

        if not huiyuan_id:
            return Response({'code': 400, 'msg': '缺少会员ID'})

        rec, created, jifen_info, err = upsert_gift_huiyuan(
            yonghu_id=dashou_id,
            huiyuan_id=huiyuan_id,
            daoqi_time=daoqi_time,
            song_ren=song_ren,
        )
        if err:
            return Response({'code': 400, 'msg': err})

        try:
            hy_name = HuiyuanModel.query.get(huiyuan_id=huiyuan_id).jieshao
        except HuiyuanModel.DoesNotExist:
            hy_name = huiyuan_id

        if jifen_info:
            write_xiugai_log(
                yonghuid=dashou_id,
                xiugaiid=operator,
                leixing=XIUGAI_LEIXING_DASHOU,
                jifen=jifen_info['after'],
                yjifen=jifen_info['before'],
                qitashuoming=(
                    f'【后台】送会员时整改积分：{jifen_info["before"]} → {jifen_info["after"]}，'
                    f'原积分={jifen_info["before"]}，整改后={jifen_info["after"]}，'
                    f'打手ID={dashou_id}，操作人={operator}（送会员免费接单）'
                ),
            )

        action_label = '赠送' if created else '更新到期时间'
        write_xiugai_log(
            yonghuid=dashou_id,
            xiugaiid=operator,
            leixing=XIUGAI_LEIXING_DASHOU,
            huiyuan_id=huiyuan_id,
            qitashuoming=(
                f'【后台】{action_label}赠送会员（免费接单）：会员ID={huiyuan_id}（{hy_name}），'
                f'到期={rec.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}，'
                f'打手ID={dashou_id}，操作人={operator}（写入赠送会员表，非购买会员）'
            ),
        )
        return Response({
            'code': 0,
            'msg': '赠送成功' if created else '到期时间已更新',
            'data': {
                'id': rec.id,
                'huiyuan_id': rec.huiyuan_id,
                'daoqi_time': rec.daoqi_time.strftime('%Y-%m-%d %H:%M:%S'),
                'created': created,
                'jifen_adjusted': bool(jifen_info),
                'jifen': jifen_info,
            },
        })


class DashouGiftHuiyuanRemoveView(APIView):
    """
    移除赠送会员
    POST /houtai/dsycsonghy
    Body: { phone, dashou_id, id? , huiyuan_id? }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        from jituan.services.gift_huiyuan import (
            has_gift_huiyuan_permission,
            remove_gift_huiyuan,
        )

        phone = (request.data.get('phone') or request.data.get('username') or '').strip()
        dashou_id = (request.data.get('dashou_id') or request.data.get('uid') or '').strip()
        record_id = request.data.get('id')
        huiyuan_id = (request.data.get('huiyuan_id') or '').strip()

        kefu, permissions = verify_kefu_permission(request, phone or None)
        if kefu is None:
            return permissions
        if not has_gift_huiyuan_permission(permissions):
            return Response({'code': 403, 'msg': '无移除赠送会员权限'})

        if not dashou_id:
            return Response({'code': 400, 'msg': '缺少打手ID'})

        try:
            dashou_user = User.query.get(UserUID=dashou_id)
        except User.DoesNotExist:
            return Response({'code': 404, 'msg': '打手不存在'})
        denied = forbid_if_user_out_of_scope(request, dashou_user)
        if denied:
            return denied

        rid = None
        if record_id not in (None, ''):
            try:
                rid = int(record_id)
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '无效的记录ID'})

        ok, err = remove_gift_huiyuan(
            record_id=rid,
            yonghu_id=dashou_id if not rid else None,
            huiyuan_id=huiyuan_id if not rid else None,
        )
        if err:
            return Response({'code': 400, 'msg': err})

        operator = getattr(kefu.user, 'Phone', None) or phone
        write_xiugai_log(
            yonghuid=dashou_id,
            xiugaiid=operator,
            leixing=XIUGAI_LEIXING_DASHOU,
            huiyuan_id=huiyuan_id or '',
            qitashuoming=(
                f'【后台】移除赠送会员（免费接单）：记录ID={rid or "-"}，'
                f'会员ID={huiyuan_id or "-"}，打手ID={dashou_id}，操作人={operator}'
            ),
        )
        return Response({'code': 0, 'msg': '移除成功'})





