"""users.views.kefu_withdraw - auto-generated by split script."""
"""users.views.kefu - auto-generated by split script."""
import os
import sys
import json
import re
import time
import uuid
import random
import string
import requests
import hashlib
import traceback
import decimal
import jwt
import ipaddress
import xmltodict
from datetime import datetime, timedelta
from decimal import Decimal
import defusedxml.ElementTree as ET

from django.conf import settings
from django.db import models, transaction, IntegrityError, DatabaseError, connection
from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch
from django.core.cache import cache
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.http import HttpResponse
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.hashers import make_password

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.authentication import JWTAuthentication

from gvsdsdk.fluent import db, func, FQ

from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from utils.money import yuan_to_fen
from utils.fadan_utils import check_fadan_qiangdan_eligible
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
from users.fadan_fenhong_utils import process_fadan_fenhong

from orders.utils import (
    update_daily_payout,
    settle_shangjia_order_guanshi_fenhong
)
from backend.utils import (
    update_dashou_daily_by_action, update_guanshi_daily_by_action,
    update_shangjia_daily, update_zuzhang_daily_by_action,
    verify_kefu_permission
)
from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan

from ..models import (
    UserBoss, UserDashou, UserShangjia, UserGuanshi,
    UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser,
    TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu,
    RankingRecord
)
from users.business_models import User
from ..tixian_shenhe_services import (
    load_audit_meta_map, refund_balance, refund_withdraw_reject,
    estimate_manual_shouxufei, sync_audit_and_jilu_status,
    mark_transfer_success, release_collect_quota_for_record,
    close_audit_wechat_failed, query_wechat_transfer_bill,
    check_shijidaozhang_within_limit,
    check_dashou_trial_blocks_commission_withdraw,
    admin_mark_pending_collect_success,
    admin_reject_pending_or_auditing,
    WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING,
)
from ..tixian_shenhe_views import process_audit_collect

from orders.models import (
    Order, PlatformOrderExt, MerchantOrderExt, CommissionRate,
    PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage,
    Penalty, PenaltyAppealImage
)
from products.models import (
    ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu
)
from config.models import Qunpeizhi
from backend.models import MerchantDailyStats
from rank.models import (
    KaohePayTemp, Chenghao, ShenheJilu,
    KaoheCishuFeiyong, YonghuChenghao
)
from users.models import UserShenheguan
from products.utils import update_shangpin_daily_stat
from shop.utils import update_dianpu_daily_stat
from rank.utils import create_shenhe_jilu_from_temp

import logging

logger = logging.getLogger(__name__)


kefu_txsh_list_logger = logging.getLogger('yonghu.kefu_txsh_list')


_TIXIAN_LIST_BASE_FIELDS = (
    'id', 'yonghuid', 'avatar', 'phone', 'nicheng', 'leixing',
    'zhifu', 'skzhanghao', 'jine', 'zhuangtai', 'fangshi',
    'shenheid', 'bhliyou', 'CreateTime', 'UpdateTime',
)


_TIXIAN_LIST_AUDIT_FIELDS = ('shenhe_jilu_id', 'shenhe_danhao')


class KefuWithdrawListView(APIView):
    """
    客服获取提现审核列表接口
    请求：POST /yonghu/kefu_txsh_list
    参数：{
        "phone": "客服手机号",
        "page": 1,
        "page_size": 20,
        "status": 1(待审核)/2(已处理),
        "type": 1(打手佣金)/2(管事分红)/3(组长分红)/4(审核官分佣)/5(打手押金)/6(商家余额),  可选
        "payment": 1(微信)/2(支付宝) 可选,
        "search_uid": "用户ID搜索" 可选,
        "date": "YYYY-MM-DD" 可选,
        "min_amount": 0,       # 最小提现金额（元）可选
        "max_amount": 999999,  # 最大提现金额（元）可选
        "dakuan_mode": 1       # 打款方式：1手动打款 2自动打款，可选
    }
    认证：JWT
    返回：{
        "code": 0,
        "data": {
            "list": [提现记录],
            "total": 总数,
            "stats": {
                "total": 总记录数,
                "awaiting": 待审核数,
                "processed": 已处理数,
                "total_amount": "总金额（元）"
            }
        }
    }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def _safe_request_params(self, request):
        try:
            return dict(request.data)
        except Exception:
            return {'raw': str(request.data)}

    def _shenhe_jilu_field_available(self):
        """探测 shenhe_jilu_id 字段是否已在数据库中就绪"""
        try:
            Tixianjilu.query.values_list('shenhe_jilu_id', flat=True).first()
            return True
        except DatabaseError as e:
            kefu_txsh_list_logger.error(
                'kefu_txsh_list: shenhe_jilu_id 字段不可用，将按旧表结构查询: %s', e,
                exc_info=True,
            )
            return False

    def _list_queryset(self, q, shenhe_field_ok, request):
        """字段未迁移时 only 旧字段，避免 SELECT 不存在的列导致 500"""
        fields = _TIXIAN_LIST_BASE_FIELDS + (
            _TIXIAN_LIST_AUDIT_FIELDS if shenhe_field_ok else ()
        )
        from jituan.services.tixian_club import filter_tixianjilu_by_request
        qs = Tixianjilu.query.filter(q).only(*fields)
        return filter_tixianjilu_by_request(qs, request)

    def _resolve_row_dakuan_mode(self, item, audit_mode_map, shenhe_field_ok):
        """无关联审核或审核表未标记自动 → 默认手动(1)"""
        if not shenhe_field_ok:
            return 1
        sid = getattr(item, 'shenhe_jilu_id', None)
        if not sid:
            return 1
        if audit_mode_map.get(sid) == 2:
            return 2
        return 1

    def post(self, request):
        from jituan.services.group_finance_gate import require_withdraw_audit_access

        dakuan_mode_filter = request.data.get('dakuan_mode')
        req_params = self._safe_request_params(request)
        kefu_txsh_list_logger.info('kefu_txsh_list 请求开始 params=%s', req_params)

        try:
            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': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST)

            if page < 1:
                page = 1
            if page_size < 1 or page_size > 100:
                page_size = min(max(page_size, 1), 100)

            status_filter = request.data.get('status')
            zhuangtai_exact = request.data.get('zhuangtai')
            type_filter = request.data.get('type')
            payment_filter = request.data.get('payment')
            search_uid = (request.data.get('search_uid') or '').strip()
            date_filter = (request.data.get('date') or '').strip()
            min_amount = request.data.get('min_amount')
            max_amount = request.data.get('max_amount')

            phone = (request.data.get('phone') or '').strip()
            if not phone:
                return Response({'code': 401, 'msg': '缺少客服手机号'}, status=status.HTTP_401_UNAUTHORIZED)

            kefu, err_resp = require_withdraw_audit_access(request, phone)
            if err_resp:
                return err_resp

            q = Q()

            try:
                status_val = int(status_filter) if status_filter is not None and str(status_filter).strip() != '' else None
            except (TypeError, ValueError):
                status_val = None

            try:
                zhuangtai_val = (
                    int(zhuangtai_exact)
                    if zhuangtai_exact is not None and str(zhuangtai_exact).strip() != ''
                    else None
                )
            except (TypeError, ValueError):
                zhuangtai_val = None

            try:
                dakuan_mode_val = int(dakuan_mode_filter) if dakuan_mode_filter is not None and str(dakuan_mode_filter).strip() != '' else None
            except (TypeError, ValueError):
                dakuan_mode_val = None

            # 未传打款方式参数 → 默认手动打款(1)
            effective_dakuan = dakuan_mode_val if dakuan_mode_val in (1, 2) else 1

            # 精确状态优先（1审核中 2成功 3失败 4审核成功 5审核失败 6待收款）
            if zhuangtai_val in (1, 2, 3, 4, 5, 6):
                q &= Q(zhuangtai=zhuangtai_val)
            elif status_val == 1:
                q &= Q(zhuangtai=1)
            elif status_val == 2:
                if effective_dakuan == 2:
                    q &= Q(zhuangtai__in=[2, 3, 4, 5, 6])
                else:
                    q &= Q(zhuangtai__in=[2, 3])
            # status_val 为空或 0：不按桶过滤（配合精确状态或看全部）

            if type_filter is not None and str(type_filter).strip() != '':
                try:
                    type_val = int(type_filter)
                    if type_val in [1, 2, 3, 4, 5, 6]:
                        q &= Q(leixing=type_val)
                except (TypeError, ValueError):
                    pass

            if payment_filter is not None and str(payment_filter).strip() != '':
                try:
                    payment_val = int(payment_filter)
                    if payment_val in [1, 2]:
                        q &= Q(fangshi=payment_val)
                except (TypeError, ValueError):
                    pass

            if search_uid:
                q &= Q(yonghuid=search_uid)

            # 按申请日筛选（CreateTime），不是处理日
            if date_filter:
                q &= Q(CreateTime__date=date_filter)

            if min_amount is not None and str(min_amount).strip() != '':
                try:
                    q &= Q(jine__gte=float(min_amount))
                except (TypeError, ValueError):
                    pass
            if max_amount is not None and str(max_amount).strip() != '':
                try:
                    q &= Q(jine__lte=float(max_amount))
                except (TypeError, ValueError):
                    pass

            shenhe_field_ok = self._shenhe_jilu_field_available()
            if shenhe_field_ok:
                if effective_dakuan == 2:
                    q &= Q(shenhe_jilu_id__isnull=False)
                else:
                    q &= Q(shenhe_jilu_id__isnull=True)
            elif effective_dakuan == 2:
                kefu_txsh_list_logger.warning('kefu_txsh_list: 自动打款筛选不可用，返回空列表')
                return Response({
                    'code': 0,
                    'data': {
                        'list': [],
                        'total': 0,
                        'stats': {'total': 0, 'awaiting': 0, 'processed': 0, 'total_amount': '0.00'},
                    },
                })

            processed_statuses = [2, 3, 4, 5, 6] if effective_dakuan == 2 else [2, 3]

            base_qs = self._list_queryset(q, shenhe_field_ok, request)
            agg = base_qs.aggregate(
                total=Count('id'),
                awaiting=Count('id', filter=Q(zhuangtai=1)),
                processed=Count('id', filter=Q(zhuangtai__in=processed_statuses)),
                total_amount=Sum('jine'),
            )

            stats = {
                'total': agg['total'],
                'awaiting': agg['awaiting'],
                'processed': agg['processed'],
                'total_amount': str(round(float(agg.get('total_amount') or 0), 2)),
            }

            queryset = base_qs.order_by('CreateTime')
            total = queryset.count()
            records = list(queryset[(page - 1) * page_size: page * page_size])

            audit_ids = [
                getattr(item, 'shenhe_jilu_id', None)
                for item in records if getattr(item, 'shenhe_jilu_id', None)
            ]
            audit_meta_map = {}
            if audit_ids:
                try:
                    audit_meta_map = load_audit_meta_map(audit_ids)
                except Exception as e:
                    kefu_txsh_list_logger.error(
                        'kefu_txsh_list: 查询审核表信息失败: %s', e,
                        exc_info=True,
                    )

            audit_mode_map = {
                aid: meta.get('dakuan_mode', 2) for aid, meta in audit_meta_map.items()
            }
            list_data = []
            for item in records:
                shenhe_jilu_id = getattr(item, 'shenhe_jilu_id', None) if shenhe_field_ok else None
                shenhe_danhao = (getattr(item, 'shenhe_danhao', None) or '') if shenhe_field_ok else ''
                meta = audit_meta_map.get(shenhe_jilu_id, {}) if shenhe_jilu_id else {}
                if shenhe_jilu_id and not shenhe_danhao:
                    shenhe_danhao = meta.get('shenhe_danhao', '')
                create_time = item.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if item.CreateTime else ''
                update_time = item.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if item.UpdateTime else ''
                list_data.append({
                    'id': item.id,
                    'shenhe_jilu_id': shenhe_jilu_id,
                    'shenhe_danhao': shenhe_danhao,
                    'yonghuid': item.yonghuid,
                    'avatar': item.avatar or '',
                    'phone': item.phone or '',
                    'nicheng': item.nicheng or '',
                    'leixing': item.leixing,
                    'zhifu': item.zhifu or '',
                    'skzhanghao': item.skzhanghao or '',
                    'jine': str(item.jine) if item.jine is not None else '0.00',
                    'shouxufei': meta.get('shouxufei', '0.00') if shenhe_jilu_id else '0.00',
                    'shijidaozhang': meta.get('shijidaozhang') or (str(item.jine) if item.jine is not None else '0.00'),
                    'zhuangtai': item.zhuangtai,
                    'fangshi': item.fangshi,
                    'dakuan_mode': self._resolve_row_dakuan_mode(item, audit_mode_map, shenhe_field_ok),
                    'shenheid': item.shenheid or '',
                    'bhliyou': item.bhliyou or '',
                    # 同时返回两种命名，兼容前后端
                    'CreateTime': create_time,
                    'UpdateTime': update_time,
                    'create_time': create_time,
                    'update_time': update_time,
                })

            from jituan.services.club_user_access import list_response_meta

            return Response({
                'code': 0,
                'data': {
                    'list': list_data,
                    'total': total,
                    'stats': stats,
                    **list_response_meta(request),
                },
            })

        except DatabaseError as e:
            kefu_txsh_list_logger.error(
                'kefu_txsh_list 数据库错误 dakuan_mode=%s params=%s err=%s',
                dakuan_mode_filter, req_params, e,
                exc_info=True,
            )
            return Response({
                'code': 500,
                'msg': '数据库查询失败，请确认已执行 python manage.py migrate yonghu',
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        except Exception as e:
            kefu_txsh_list_logger.error(
                'kefu_txsh_list 接口异常 dakuan_mode=%s params=%s err=%s',
                dakuan_mode_filter, req_params, e,
                exc_info=True,
            )
            return Response({
                'code': 500,
                'msg': '获取提现列表失败',
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class KefuWithdrawDetailView(APIView):
    """
    客服获取提现详情接口
    请求：POST /yonghu/kefu_txsh_detail
    参数：{
        "phone": "客服手机号",
        "tixian_id": "提现记录ID"
    }
    认证：JWT
    返回：code=0 + 提现记录详情 + 用户扩展信息
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        phone = request.data.get('phone', '').strip()
        tixian_id = request.data.get('tixian_id')

        if not phone or not tixian_id:
            return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)

        # 客服身份验证
        current_user = request.user
        if getattr(current_user, 'Phone', '') != phone:
            logger.warning(f"手机号不匹配: {phone}")
            return Response({'code': 403, 'msg': '身份不匹配'}, status=status.HTTP_403_FORBIDDEN)

        from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin
        if not is_kefu_backend_account(current_user):
            logger.warning(f"非后台账号: {current_user.UserUID}")
            return Response({'code': 403, 'msg': '非后台账号'}, status=status.HTTP_403_FORBIDDEN)

        is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin'
        kefu = None
        if not is_admin:
            try:
                kefu = current_user.KefuProfile
            except ObjectDoesNotExist:
                logger.warning(f"客服扩展表不存在: {current_user.UserUID}")
                return Response({'code': 403, 'msg': '客服账号不存在'}, status=status.HTTP_403_FORBIDDEN)

            if kefu.zhuangtai != 1:
                logger.warning(f"客服账号已禁用: {current_user.UserUID}")
                return Response({'code': 403, 'msg': '客服账号已被禁用'}, status=status.HTTP_403_FORBIDDEN)

        from jituan.services.group_finance_gate import require_withdraw_audit_access
        _, err_resp = require_withdraw_audit_access(request, phone)
        if err_resp:
            return err_resp

        # 查询提现记录
        try:
            record = Tixianjilu.query.get(id=tixian_id)
        except Tixianjilu.DoesNotExist:
            return Response({'code': 404, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND)

        from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
        deny = forbid_if_tixian_out_of_scope(request, record)
        if deny:
            return deny

        # 查询用户主表
        try:
            user = User.query.get(UserUID=record.yonghuid)
        except User.DoesNotExist:
            user = None

        # 构建基础返回数据（字段必须与前端一致；时间同时给 snake_case）
        create_time = record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else ''
        update_time = record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else ''
        data = {
            'id': record.id,
            'yonghuid': record.yonghuid,
            'avatar': record.avatar or '',
            'phone': record.phone or '',
            'nicheng': record.nicheng or '',
            'leixing': record.leixing,          # 1打手 2管事
            'zhifu': record.zhifu or '',
            'skzhanghao': record.skzhanghao or '',
            'jine': str(record.jine) if record.jine else '0.00',
            'zhuangtai': record.zhuangtai,
            'fangshi': record.fangshi,
            'shenheid': record.shenheid or '',
            'bhliyou': record.bhliyou or '',
            'CreateTime': create_time,
            'UpdateTime': update_time,
            'create_time': create_time,
            'update_time': update_time,
            'shenhe_jilu_id': getattr(record, 'shenhe_jilu_id', None),
            'shenhe_danhao': getattr(record, 'shenhe_danhao', None) or '',
            'dakuan_mode': 2 if getattr(record, 'shenhe_jilu_id', None) else 1,
            'shouxufei': '0.00',
            'shijidaozhang': str(record.jine) if record.jine else '0.00',
        }

        sid = getattr(record, 'shenhe_jilu_id', None)
        if sid:
            try:
                meta_map = load_audit_meta_map([sid])
                meta = meta_map.get(sid) or {}
                data['shenhe_danhao'] = data['shenhe_danhao'] or meta.get('shenhe_danhao', '')
                data['shouxufei'] = meta.get('shouxufei', '0.00')
                data['shijidaozhang'] = meta.get('shijidaozhang') or data['shijidaozhang']
                data['dakuan_mode'] = meta.get('dakuan_mode', 2) or 2
            except Exception:
                pass

        # 根据提现类型添加扩展信息
        if user:
            if record.leixing == 1:  # 打手
                try:
                    dashou = UserDashou.query.get(user=user)
                    # 计算成交率和退款率
                    total = dashou.jiedanzongliang or 0
                    completed = dashou.chengjiaozongliang or 0
                    refund = dashou.tuikuanliang or 0
                    completion_rate = round((completed / total * 100) if total > 0 else 0)
                    refund_rate = round((refund / completed * 100) if completed > 0 else 0)

                    data['dashouInfo'] = {
                        'zhuangtai': dashou.zhuangtai,                     # 工作状态
                        'zaixianzhuangtai': dashou.zaixianzhuangtai,       # 在线状态
                        'zhanghaozhuangtai': dashou.zhanghaozhuangtai,     # 账号状态
                        'yue': float(dashou.yue) if dashou.yue else 0.00,  # 可提现余额
                        'zonge': float(dashou.zonge) if dashou.zonge else 0.00,  # 接单总额
                        'jifen': dashou.jifen,                             # 积分
                        'jiedanzongliang': dashou.jiedanzongliang,         # 接单总量
                        'chengjiaozongliang': dashou.chengjiaozongliang,   # 成交总量
                        'tuikuanliang': dashou.tuikuanliang,               # 退款总量
                        'completionRate': completion_rate,
                        'refundRate': refund_rate,
                    }
                except ObjectDoesNotExist:
                    data['dashouInfo'] = None
            elif record.leixing == 2:  # 管事
                try:
                    guanshi = UserGuanshi.query.get(user=user)
                    data['guanshiInfo'] = {
                        'zhuangtai': guanshi.zhuangtai,                    # 账号状态
                        'yue': float(guanshi.yue) if guanshi.yue else 0.00,
                        'yaogingshuliang': guanshi.yaogingshuliang,        # 邀请打手总数
                        'jinrichongzhi': guanshi.jinrichongzhi,            # 今日充值
                        'jinyuechongzhi': guanshi.jinyuechongzhi,          # 今月充值
                        'chongzhifenrun': float(guanshi.chongzhifenrun) if guanshi.chongzhifenrun else 0.00,
                    }
                except ObjectDoesNotExist:
                    data['guanshiInfo'] = None

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


class KefuWithdrawActionView(APIView):
    """
    客服处理提现接口（同意/拒绝）—— 仅此接口处理后台审核打款
    POST /yonghu/kefu_txsh_action

    单条：{ phone, action, tixian_id, yonghuid, leixing, reason? }
    批量：{ phone, action, batch_list: [{tixian_id, yonghuid, leixing}, ...], reason? }

    action:
      1 = 同意（审核中→待收款）
      2 = 驳回（审核中/待收款→审核失败）
      3 = 标记已收款（仅待收款→提现成功）

    驳回(action=2)可选：
      refund_principal: 是否返还到账本金（默认 true）
      refund_fee: 是否退还手续费（默认 false）
      apply_entry_freeze: 退回余额是否走入账冻结（默认 false）
      三者均可关：只改状态不退钱

    自动打款(dakuan_mode=2)：同意→6待收款；拒绝→5；标记已收款→2
    手动打款(dakuan_mode=1)：原逻辑不变（仅审核中）
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    @staticmethod
    def _parse_bool(raw, default=False):
        if raw is None:
            return bool(default)
        if isinstance(raw, bool):
            return raw
        if isinstance(raw, (int, float)):
            return bool(raw)
        s = str(raw).strip().lower()
        if s in ('1', 'true', 'yes', 'y', 'on'):
            return True
        if s in ('0', 'false', 'no', 'n', 'off', ''):
            return False
        return bool(default)

    def _verify_kefu(self, request, phone):
        """与 menu-access / verify_kefu_permission 对齐：按后台账号放行，不因 UserType 推断失败而 401。"""
        from jituan.services.admin_context import is_kefu_backend_account, is_system_super_admin
        if not phone:
            return None, Response({'code': 400, 'msg': '手机号不能为空'}, status=status.HTTP_400_BAD_REQUEST)
        current_user = request.user
        if getattr(current_user, 'Phone', '') != phone:
            return None, Response({'code': 403, 'msg': '身份不匹配'}, status=status.HTTP_403_FORBIDDEN)
        if not is_kefu_backend_account(current_user):
            return None, Response({'code': 403, 'msg': '非后台账号'}, status=status.HTTP_403_FORBIDDEN)
        is_admin = is_system_super_admin(current_user) or current_user.UserType == 'admin'
        if not is_admin:
            try:
                kefu = current_user.KefuProfile
            except ObjectDoesNotExist:
                return None, Response({'code': 403, 'msg': '客服账号不存在'}, status=status.HTTP_403_FORBIDDEN)
            if kefu.zhuangtai != 1:
                return None, Response({'code': 403, 'msg': '客服账号已被禁用'}, status=status.HTTP_403_FORBIDDEN)
        return current_user, None

    def _parse_items(self, request):
        """解析单条或 batch_list，返回 [{tixian_id, yonghuid, leixing}]"""
        batch_list = request.data.get('batch_list')
        if batch_list is not None:
            if not isinstance(batch_list, list) or len(batch_list) == 0:
                return None, Response({'code': 400, 'msg': 'batch_list 不能为空'}, status=status.HTTP_400_BAD_REQUEST)
            items = []
            for idx, row in enumerate(batch_list):
                if not isinstance(row, dict):
                    return None, Response({'code': 400, 'msg': f'batch_list[{idx}] 格式错误'}, status=status.HTTP_400_BAD_REQUEST)
                tid = row.get('tixian_id')
                yid = (row.get('yonghuid') or '').strip()
                lx = row.get('leixing')
                if not tid or not yid or lx is None:
                    return None, Response(
                        {'code': 400, 'msg': f'batch_list[{idx}] 缺少 tixian_id / yonghuid / leixing'},
                        status=status.HTTP_400_BAD_REQUEST,
                    )
                try:
                    items.append({
                        'tixian_id': int(tid),
                        'yonghuid': yid,
                        'leixing': int(lx),
                    })
                except (TypeError, ValueError):
                    return None, Response({'code': 400, 'msg': f'batch_list[{idx}] 参数格式错误'}, status=status.HTTP_400_BAD_REQUEST)
            return items, None

        tixian_id = request.data.get('tixian_id')
        if not tixian_id:
            return None, Response({'code': 400, 'msg': '提现记录ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
        yonghuid = (request.data.get('yonghuid') or '').strip()
        leixing = request.data.get('leixing')
        try:
            item = {'tixian_id': int(tixian_id), 'yonghuid': yonghuid, 'leixing': int(leixing) if leixing is not None else None}
        except (TypeError, ValueError):
            return None, Response({'code': 400, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST)
        return [item], None

    def _process_auto_item(self, request, item, action, reason, kefu_user,
                           refund_fee=False, apply_entry_freeze=False,
                           refund_principal=True, with_accounting=True):
        """自动打款单条处理（须在 transaction.atomic 内调用）"""
        tixian_id = item['tixian_id']
        req_yonghuid = item['yonghuid']
        req_leixing = item['leixing']

        if req_leixing not in [1, 2, 3, 4, 5, 6]:
            raise ValueError(f'提现记录{tixian_id}：提现类型无效')

        try:
            tixian = Tixianjilu.objects.select_for_update().get(id=tixian_id)
        except Tixianjilu.DoesNotExist:
            raise ValueError(f'提现记录{tixian_id}不存在')

        from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
        deny = forbid_if_tixian_out_of_scope(request, tixian)
        if deny:
            raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))

        if tixian.yonghuid != req_yonghuid:
            raise ValueError(f'提现记录{tixian_id}：用户ID不匹配')
        if tixian.leixing != req_leixing:
            raise ValueError(f'提现记录{tixian_id}：提现类型不匹配')
        if not tixian.shenhe_jilu_id:
            raise ValueError(f'提现记录{tixian_id}：非自动打款审核记录')

        try:
            audit = TixianShenheJilu.objects.select_for_update().get(id=tixian.shenhe_jilu_id)
        except TixianShenheJilu.DoesNotExist:
            raise ValueError(f'提现记录{tixian_id}：审核记录不存在')

        if audit.yonghuid != req_yonghuid:
            raise ValueError(f'提现记录{tixian_id}：审核记录用户ID不匹配')
        if audit.leixing != req_leixing:
            raise ValueError(f'提现记录{tixian_id}：审核记录提现类型不匹配')

        try:
            user = User.objects.select_for_update().get(UserUID=req_yonghuid)
        except User.DoesNotExist:
            raise ValueError(f'用户{req_yonghuid}不存在')

        cur = int(tixian.zhuangtai)
        kefu_uid = getattr(kefu_user, 'UserUID', '') or ''

        # action=3：待收款 → 已收款
        if action == 3:
            if cur != 6 or int(audit.zhuangtai) != 6:
                raise ValueError(f'提现记录{tixian_id}：仅待收款可标记已收款')
            admin_mark_pending_collect_success(
                audit, tixian, kefu_uid, with_accounting=with_accounting,
            )
            return

        # action=2：审核中/待收款 → 驳回
        if action == 2:
            if cur not in (1, 6) or int(audit.zhuangtai) not in (1, 6):
                raise ValueError(
                    f'提现记录{tixian_id}：仅审核中/待收款可驳回'
                    f'(当前提现单={cur},审核单={audit.zhuangtai})'
                )
            admin_reject_pending_or_auditing(
                audit, tixian, user, kefu_uid, reason,
                refund_principal=refund_principal,
                refund_fee=refund_fee,
                apply_entry_freeze=apply_entry_freeze,
            )
            return

        # action=1：仅审核中 → 待收款
        if action == 1:
            if cur != 1 or int(audit.zhuangtai) != 1:
                raise ValueError(f'提现记录{tixian_id}：非审核中状态，无法同意')
            limit_ok, limit_msg = check_shijidaozhang_within_limit(audit.shijidaozhang)
            if not limit_ok:
                admin_reject_pending_or_auditing(
                    audit, tixian, user, kefu_uid, limit_msg,
                    refund_principal=True,
                    refund_fee=False,
                    apply_entry_freeze=False,
                )
                raise ValueError(f'提现记录{tixian_id}：{limit_msg}，已自动驳回并退款')
            sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_uid)
            tixian.shenheid = kefu_uid
            tixian.save(update_fields=['shenheid', 'UpdateTime'])
            return

        raise ValueError(f'不支持的 action={action}')

    def _process_manual_item(self, request, item, action, reason, phone,
                             refund_fee=False, apply_entry_freeze=False,
                             refund_principal=True):
        """手动打款单条处理（原逻辑，须在 transaction.atomic 内调用）"""
        tixian_id = item['tixian_id']
        try:
            tixian = Tixianjilu.objects.select_for_update().get(id=tixian_id)
        except Tixianjilu.DoesNotExist:
            raise ValueError(f'提现记录{tixian_id}不存在')

        from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
        deny = forbid_if_tixian_out_of_scope(request, tixian)
        if deny:
            raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))

        req_yonghuid = item.get('yonghuid')
        req_leixing = item.get('leixing')
        if req_yonghuid and tixian.yonghuid != req_yonghuid:
            raise ValueError(f'提现记录{tixian_id}：用户ID不匹配')
        if req_leixing is not None and tixian.leixing != req_leixing:
            raise ValueError(f'提现记录{tixian_id}：提现类型不匹配')
        if tixian.zhuangtai != 1:
            raise ValueError(f'提现记录{tixian_id}：非审核中状态，无法处理')

        if getattr(tixian, 'shenhe_jilu_id', None):
            raise ValueError(f'提现记录{tixian_id}：自动打款请走自动流程')

        try:
            user = User.query.get(UserUID=tixian.yonghuid)
        except User.DoesNotExist:
            raise ValueError(f'用户{tixian.yonghuid}不存在')

        from jituan.services.tixian_club import resolve_payout_club_id

        if action == 1:
            tixian.zhuangtai = 2
            try:
                from jituan.services.szjilu_accounting import apply_szjilu_expense
                payout_club = resolve_payout_club_id(jilu=tixian, user=user)
                update_daily_payout(tixian.jine, payout_club)
                apply_szjilu_expense(tixian.jine, payout_club)
            except Exception as e:
                logger.error(f'更新收支记录失败: {e}')
        else:
            # 先改状态，再按选项退款
            tixian.zhuangtai = 3
            tixian.bhliyou = reason
            if refund_principal or refund_fee:
                fee = estimate_manual_shouxufei(user, tixian.leixing, tixian.jine) if refund_fee else Decimal('0')
                principal = tixian.jine if refund_principal else Decimal('0')
                try:
                    refund_withdraw_reject(
                        user,
                        tixian.leixing,
                        shijidaozhang=principal,
                        shouxufei=fee,
                        refund_fee=refund_fee,
                        apply_entry_freeze=apply_entry_freeze,
                        biz_id=f'withdraw_reject:manual:{tixian_id}',
                        freeze_reason=f'提现驳回退款 #{tixian_id}',
                    )
                except ValueError as e:
                    raise ValueError(str(e))

        tixian.shenheid = phone
        tixian.UpdateTime = timezone.now()
        tixian.save()

    def post(self, request):
        logger.info(f'接收到提现处理请求，数据: {request.data}')

        phone = request.data.get('phone', '').strip()
        action_raw = request.data.get('action')
        reason = request.data.get('reason', '').strip()
        refund_fee = self._parse_bool(request.data.get('refund_fee'), False)
        apply_entry_freeze = self._parse_bool(request.data.get('apply_entry_freeze'), False)
        # 驳回默认返还本金；可显式关掉只改状态
        refund_principal = self._parse_bool(request.data.get('refund_principal'), True)
        with_accounting = self._parse_bool(request.data.get('with_accounting'), True)

        try:
            action = int(action_raw)
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': 'action 参数必须为数字'}, status=status.HTTP_400_BAD_REQUEST)
        if action not in [1, 2, 3]:
            return Response(
                {'code': 400, 'msg': 'action 必须为1(同意)/2(驳回)/3(标记已收款)'},
                status=status.HTTP_400_BAD_REQUEST,
            )
        if action == 2 and not reason:
            return Response({'code': 400, 'msg': '拒绝提现时理由不能为空'}, status=status.HTTP_400_BAD_REQUEST)

        kefu_user, err_resp = self._verify_kefu(request, phone)
        if err_resp:
            return err_resp

        from jituan.services.group_finance_gate import require_withdraw_audit_access
        _, audit_err = require_withdraw_audit_access(request, phone)
        if audit_err:
            return audit_err

        items, err_resp = self._parse_items(request)
        if err_resp:
            return err_resp

        is_batch = isinstance(request.data.get('batch_list'), list) and len(request.data.get('batch_list')) > 0

        try:
            with transaction.atomic():
                for item in items:
                    tixian_id = item['tixian_id']
                    try:
                        tixian_probe = Tixianjilu.objects.select_for_update().get(id=tixian_id)
                    except Tixianjilu.DoesNotExist:
                        raise ValueError(f'提现记录{tixian_id}不存在')

                    from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
                    deny = forbid_if_tixian_out_of_scope(request, tixian_probe)
                    if deny:
                        raise ValueError(deny.data.get('msg', '无权限处理该提现记录'))

                    # 有关联审核记录(shenhe_jilu_id)即为 zddksh 自动打款，不依赖 dakuan_mode 字段
                    is_auto = bool(getattr(tixian_probe, 'shenhe_jilu_id', None))

                    if is_auto:
                        self._process_auto_item(
                            request, item, action, reason, kefu_user,
                            refund_fee=refund_fee if action == 2 else False,
                            apply_entry_freeze=apply_entry_freeze if action == 2 else False,
                            refund_principal=refund_principal if action == 2 else True,
                            with_accounting=with_accounting if action == 3 else True,
                        )
                    else:
                        if action == 3:
                            raise ValueError(f'提现记录{tixian_id}：手动打款无「待收款」，不能标记已收款')
                        if is_batch:
                            raise ValueError(f'提现记录{tixian_id}：批量操作仅支持自动打款记录')
                        self._process_manual_item(
                            request, item, action, reason, phone,
                            refund_fee=refund_fee if action == 2 else False,
                            apply_entry_freeze=apply_entry_freeze if action == 2 else False,
                            refund_principal=refund_principal if action == 2 else True,
                        )

        except ValueError as e:
            logger.warning('kefu_txsh_action 业务拒绝: %s data=%s', e, request.data)
            return Response({'code': 400, 'msg': str(e)}, status=status.HTTP_400_BAD_REQUEST)
        except Exception as e:
            logger.error(f'处理提现事务失败: {e}', exc_info=True)
            return Response({'code': 500, 'msg': f'处理失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        msg = '批量处理成功' if is_batch and len(items) > 1 else '处理成功'
        return Response({'code': 0, 'msg': msg, 'data': {'count': len(items)}})
