"""集团平台 API 视图。"""
import logging

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

from jituan.constants import SUPER_ADMIN_PHONES, ADMIN_ROLE_LABELS, CLUB_ID_DEFAULT, DATA_SCOPE_ALL, DATA_SCOPE_SINGLE
from jituan.models import Club, AdminAssignment
from jituan.services.admin_context import build_admin_club_context, is_kefu_backend_account, can_manage_admin_assignments
from jituan.services.kefu_menu import build_menu_access_payload
from jituan.services.wechat_login import (
    login_or_register_by_club,
    login_or_register_by_official_oauth,
    refresh_wx_openid_binding,
)
from jituan.services.caiwu_stats import build_caiwu_payload
from jituan.services.szxx_stats import build_szxx_payload
from jituan.services.admin_assignments import (
    create_or_reactivate_assignment,
    deactivate_assignment,
    dedupe_active_assignments,
)
from jituan.services.user_stats import build_user_summary
from jituan.services.finance_detail_stats import (
    build_chongzhi_finance_payload,
    build_huiyuan_bankuai_payload,
    build_huiyuan_stats_payload,
    build_order_finance_payload,
    build_order_types_payload,
)
from backend.utils import verify_kefu_permission
from users.business_models import User

logger = logging.getLogger(__name__)


def _client_ip(request):
    xff = request.META.get('HTTP_X_FORWARDED_FOR')
    if xff:
        return xff.split(',')[0].strip()
    return request.META.get('REMOTE_ADDR', '')


class ClubWechatLoginView(APIView):
    """
    多俱乐部微信小程序登录（新接口，旧 /yonghu/wechatlogin 不变）。
    POST /jituan/auth/wechat-login
    body: { code, club_id?, app_id? }
    """
    throttle_classes = [AnonRateThrottle]
    permission_classes = [AllowAny]

    def post(self, request):
        code = (request.data.get('code') or '').strip()
        club_id = (request.data.get('club_id') or '').strip() or None
        app_id = (request.data.get('app_id') or '').strip() or None

        if not code:
            return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)

        try:
            data, err = login_or_register_by_club(
                code, club_id=club_id, app_id=app_id, client_ip=_client_ip(request),
            )
            if err:
                # 配置缺失 / 微信侧错误：明确 400，方便小程序提示运营配 AppSecret
                return Response({'code': 2, 'msg': f'微信登录失败: {err}', 'data': None},
                                status=status.HTTP_400_BAD_REQUEST)
            return Response({'code': 0, 'msg': '登录成功', 'data': data})
        except Exception as e:
            logger.exception('ClubWechatLoginView 异常')
            return Response({
                'code': 99,
                'msg': f'登录异常: {type(e).__name__}: {e}',
                'data': None,
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class ClubWechatOAuthLoginView(APIView):
    """
    服务号网页授权登录（H5 UniApp）。
    POST /jituan/auth/wechat-oauth-login
    body: { code, club_id? }
    不改动 wechat-login；仅新增本接口。
    """
    throttle_classes = [AnonRateThrottle]
    permission_classes = [AllowAny]

    def post(self, request):
        code = (request.data.get('code') or '').strip()
        club_id = (request.data.get('club_id') or '').strip() or None
        if not code:
            return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)
        try:
            data, err = login_or_register_by_official_oauth(
                code, club_id=club_id, client_ip=_client_ip(request),
            )
            if err:
                return Response({'code': 2, 'msg': f'微信登录失败: {err}', 'data': None},
                                status=status.HTTP_400_BAD_REQUEST)
            return Response({'code': 0, 'msg': '登录成功', 'data': data})
        except Exception as e:
            logger.exception('ClubWechatOAuthLoginView 异常')
            return Response({
                'code': 99,
                'msg': f'登录异常: {type(e).__name__}: {e}',
                'data': None,
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class ClubWxBindRefreshView(APIView):
    """
    已登录用户静默刷新当前小程序 openid 绑定（支付前 AppID/openid 对齐）。
    POST /jituan/auth/refresh-wx-bind
    body: { code, club_id?, app_id? }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        code = (request.data.get('code') or '').strip()
        club_id = (request.data.get('club_id') or '').strip() or None
        app_id = (request.data.get('app_id') or '').strip() or None
        if not code:
            return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)
        try:
            data, err = refresh_wx_openid_binding(
                code, request.user, club_id=club_id, app_id=app_id,
            )
            if err:
                return Response({'code': 2, 'msg': err, 'data': None},
                                status=status.HTTP_400_BAD_REQUEST)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubWxBindRefreshView 异常')
            return Response({'code': 99, 'msg': '刷新绑定失败', 'data': None},
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class ClubKefuLoginView(APIView):
    """
    多俱乐部客服/集团后台登录（新接口，旧 /yonghu/kefujinru 不变）。
    POST /jituan/auth/kefu-login
    body: { phone, password, erjimima }
    """
    throttle_classes = [AnonRateThrottle]
    permission_classes = [AllowAny]

    def post(self, request):
        phone = (request.data.get('phone') or '').strip()
        password = request.data.get('password') or ''
        erjimima = request.data.get('erjimima') or ''

        if not phone or not password:
            return Response({'code': 1, 'msg': '请填写手机号和密码', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)

        user_mains = list(
            User.query.filter(Phone=phone, KefuProfile__isnull=False).select_related('KefuProfile')
        )
        if not user_mains:
            admin_candidates = User.query.filter(Phone=phone)
            for admin_user in admin_candidates:
                if admin_user.IsSuperuser or admin_user.UserType == 'admin' or admin_user.Phone in SUPER_ADMIN_PHONES:
                    user_mains = [admin_user]
                    break

        if not user_mains:
            return Response({'code': 1, 'msg': '用户不存在', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)

        target_user = None
        target_kefu = None
        password_matched = False

        for user in user_mains:
            if not user.CheckPassword(password):
                continue
            password_matched = True

            from jituan.services.admin_context import is_hardcoded_super_phone
            try:
                kefu_probe = user.KefuProfile
            except Exception:
                kefu_probe = None
            if kefu_probe is not None and int(getattr(kefu_probe, 'zhuangtai', 1) or 0) != 1:
                if not is_hardcoded_super_phone(user):
                    if target_user is None:
                        target_user = user
                        target_kefu = kefu_probe
                    continue

            is_admin = user.IsSuperuser or user.UserType == 'admin' or user.Phone in SUPER_ADMIN_PHONES
            if is_admin:
                target_user = user
                target_kefu = kefu_probe
                break
            kefu = kefu_probe
            if kefu is None:
                continue
            stored_erji = kefu.erjimima or ''
            if stored_erji.startswith(('$2b$', '$2a$')) and len(stored_erji) == 60:
                import bcrypt as _bcrypt
                if not _bcrypt.checkpw(erjimima.encode('utf-8'), stored_erji.encode('utf-8')):
                    continue
            elif stored_erji != erjimima:
                continue
            if kefu.zhuangtai != 1:
                if target_user is None:
                    target_user = user
                    target_kefu = kefu
                continue
            target_user = user
            target_kefu = kefu
            break
        else:
            if target_user and target_kefu and int(getattr(target_kefu, 'zhuangtai', 1) or 0) != 1:
                return Response({'code': 4, 'msg': '账号已被封禁', 'data': None},
                                status=status.HTTP_403_FORBIDDEN)
            if password_matched:
                return Response({'code': 1, 'msg': '二级密码错误', 'data': None},
                                status=status.HTTP_400_BAD_REQUEST)
            return Response({'code': 1, 'msg': '密码错误', 'data': None},
                            status=status.HTTP_400_BAD_REQUEST)

        target_user.IP = _client_ip(request)
        target_user.UserLastLoginDate = timezone.now()
        target_user.save(update_fields=['IP', 'UserLastLoginDate'])

        refresh = RefreshToken.for_user(target_user)
        token = str(refresh.access_token)
        nicheng = target_kefu.nicheng if target_kefu else (target_user.UserName or target_user.Phone)
        club_context = build_admin_club_context(target_user)
        # 登录请求尚未带 JWT，需用本次登录用户 + 主任职俱乐部计算菜单
        request.user = target_user
        request.club_id = club_context.get('club_id') or CLUB_ID_DEFAULT
        request.club_scope = club_context.get('scope') or DATA_SCOPE_SINGLE
        menu_access, _menu_err = build_menu_access_payload(request, phone)

        return Response({
            'code': 0,
            'msg': '登录成功',
            'data': {
                'token': token,
                'nicheng': nicheng,
                'yonghuid': target_user.UserUID,
                'club_context': club_context,
                'menu_access': menu_access if menu_access else {
                    'visible_page_ids': [],
                    'visible_paths': [],
                    'permission_codes': [],
                    'role_names': [],
                    'yonghuid': target_user.UserUID or '',
                    'can_switch_club': False,
                    'is_group_admin': False,
                    'menu_ready': False,
                },
            },
        })


class ClubListView(APIView):
    """GET /jituan/club/list — 启用中的俱乐部列表（登录前后均可读）。"""
    permission_classes = [AllowAny]

    def get(self, request):
        clubs = Club.query.filter(status=1).order_by('sort_order', 'club_id')
        data = [
            {
                'club_id': c.club_id,
                'name': c.name,
                'wx_appid': c.wx_appid,
                'h5_domain': c.h5_domain,
            }
            for c in clubs
        ]
        return Response({'code': 0, 'msg': 'ok', 'data': data})


class ClubDefaultInviteView(APIView):
    """
    GET/POST /jituan/club/default-invite
    小程序登录页拉取当前俱乐部默认打手邀请码（AllowAny）。
    俱乐部由 X-Club-Id / AppID 解析；码来自 config_json.default_dashou_invite 或内置回落。
    """
    permission_classes = [AllowAny]
    throttle_classes = [AnonRateThrottle]

    def _payload(self, request):
        from jituan.services.club_resolver import resolve_club_id_for_wechat_request
        from jituan.services.default_invite import get_club_default_dashou_invite

        club_id = resolve_club_id_for_wechat_request(request)
        invite = get_club_default_dashou_invite(club_id)
        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'club_id': club_id or '',
                'inviteCode': invite,
            },
        })

    def get(self, request):
        return self._payload(request)

    def post(self, request):
        return self._payload(request)


class ClubMeContextView(APIView):
    """GET /jituan/auth/me-context — 已登录客服刷新俱乐部上下文。"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        if not is_kefu_backend_account(user):
            return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
        return Response({
            'code': 0,
            'msg': 'ok',
            'data': build_admin_club_context(user),
        })


class ClubKefuMenuAccessView(APIView):
    """GET /jituan/auth/menu-access — 已登录客服可见侧栏菜单。"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        if not is_kefu_backend_account(user):
            return Response({'code': 403, 'msg': '非后台账号', 'data': None}, status=403)
        payload, err = build_menu_access_payload(request)
        if err is not None:
            return err
        return Response({'code': 0, 'msg': 'ok', 'data': payload})


class ClubPermDebugView(APIView):
    """GET /jituan/auth/perm-debug — 当前俱乐部权限解析明细。"""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        from backend.utils import verify_kefu_permission
        from jituan.services.admin_context import get_allowed_club_ids, resolve_admin_effective_club
        from jituan.services.club_context import resolve_club_id_from_request, resolve_club_scope
        from jituan.services.club_rbac import describe_roles_in_scope

        user = request.user
        if not is_kefu_backend_account(user):
            return Response({'code': 403, 'msg': '非后台账号'}, status=403)

        requested_club = resolve_club_id_from_request(request)
        requested_scope = resolve_club_scope(request)
        eff_club, eff_scope, _ = resolve_admin_effective_club(user, request)
        allowed = list(get_allowed_club_ids(user))
        _, permissions = verify_kefu_permission(request, None)
        codes = list(permissions) if isinstance(permissions, list) else ['*']

        return Response({
            'code': 0,
            'msg': 'ok',
            'data': {
                'yonghuid': user.UserUID,
                'phone': user.Phone,
                'requested_club_id': requested_club,
                'requested_scope': requested_scope,
                'effective_club_id': eff_club,
                'effective_scope': eff_scope,
                'allowed_club_ids': allowed,
                'permission_codes': codes,
                'roles_in_scope': describe_roles_in_scope(user, eff_club, eff_scope, allowed),
            },
        })


class ClubAdminAssignmentListView(APIView):
    """POST /jituan/houtai/admin-assignments — 数据范围任职（非 gvsdsdk 功能权限）。"""

    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def _is_super(self, user):
        return (
            bool(user.IsSuperuser)
            or user.UserType == 'admin'
            or getattr(user, 'Phone', '') in SUPER_ADMIN_PHONES
        )

    def _list_rows(self, assignments):
        club_name_map = {c.club_id: c.name for c in Club.query.filter(status=1)}
        uids = list({a.yonghuid for a in assignments})
        user_map = {}
        if uids:
            for u in User.query.filter(UserUID__in=uids).only('UserUID', 'Phone', 'UserName'):
                user_map[u.UserUID] = u.Phone or u.UserName or u.UserUID
        rows = []
        for a in assignments:
            club_label = '集团（全部子公司）'
            if a.club_id:
                club_label = club_name_map.get(a.club_id, a.club_id)
            rows.append({
                'id': a.id,
                'yonghuid': a.yonghuid,
                'phone': user_map.get(a.yonghuid, ''),
                'club_id': a.club_id,
                'club_label': club_label,
                'role_code': a.role_code,
                'role_name': ADMIN_ROLE_LABELS.get(a.role_code, a.role_code),
                'data_scope': a.data_scope,
                'is_primary': a.is_primary,
            })
        return rows

    def post(self, request):
        try:
            username = (request.data.get('phone') or request.data.get('username') or '').strip()
            if not username:
                return Response({'code': 401, 'msg': '缺少账号'}, status=401)
            kefu_obj, permissions = verify_kefu_permission(request, username)
            if kefu_obj is None:
                return permissions

            user = request.user
            is_super = self._is_super(user)
            action = (request.data.get('action') or 'list').strip()

            if action in ('create', 'update', 'delete'):
                if not can_manage_admin_assignments(user, permissions):
                    return Response({'code': 403, 'msg': '无权限维护任职数据（需超管或集团超管任职）'}, status=403)

                if action == 'create':
                    yonghuid = (request.data.get('yonghuid') or '').strip()
                    phone = (request.data.get('target_phone') or '').strip()
                    if not yonghuid and phone:
                        target = User.query.filter(Phone=phone).first()
                        if not target:
                            return Response({'code': 404, 'msg': f'未找到手机号 {phone}'})
                        yonghuid = target.UserUID
                    if not yonghuid:
                        return Response({'code': 400, 'msg': '需要 yonghuid 或 target_phone'})
                    club_id = request.data.get('club_id')
                    if club_id is None or str(club_id).strip() == '':
                        club_id = ''
                    else:
                        club_id = str(club_id).strip()
                    role_code = (request.data.get('role_code') or 'CLUB_ADMIN').strip()
                    data_scope = (request.data.get('data_scope') or DATA_SCOPE_SINGLE).strip()
                    if not club_id:
                        data_scope = DATA_SCOPE_ALL
                    is_primary = bool(request.data.get('is_primary', False))
                    _, err = create_or_reactivate_assignment(
                        yonghuid=yonghuid,
                        club_id=club_id,
                        role_code=role_code,
                        data_scope=data_scope,
                        is_primary=is_primary,
                        granted_by=user.UserUID,
                    )
                    if err:
                        return Response({'code': 400, 'msg': err})
                    from jituan.services.admin_assignments import dedupe_active_assignments
                    dedupe_active_assignments()
                    return Response({'code': 0, 'msg': '任职已添加'})

                if action == 'delete':
                    assignment_id = request.data.get('id')
                    if not assignment_id:
                        return Response({'code': 400, 'msg': '缺少任职 id'})
                    ok, err = deactivate_assignment(assignment_id)
                    if not ok:
                        return Response({'code': 404, 'msg': err or '停用失败'})
                    from jituan.services.admin_assignments import dedupe_active_assignments
                    dedupe_active_assignments()
                    return Response({'code': 0, 'msg': '任职已删除'})

                if action == 'update':
                    assignment_id = request.data.get('id')
                    if not assignment_id:
                        return Response({'code': 400, 'msg': '缺少任职 id'})
                    row = AdminAssignment.query.filter(id=assignment_id, status=1).first()
                    if not row:
                        return Response({'code': 404, 'msg': '任职记录不存在'})
                    if 'role_code' in request.data:
                        row.role_code = (request.data.get('role_code') or '').strip() or row.role_code
                    if 'data_scope' in request.data:
                        row.data_scope = request.data.get('data_scope') or row.data_scope
                    if 'is_primary' in request.data:
                        row.is_primary = bool(request.data.get('is_primary'))
                        if row.is_primary:
                            from jituan.services.admin_assignments import clear_other_primary_assignments
                            clear_other_primary_assignments(row.yonghuid, except_id=row.id)
                    if 'club_id' in request.data:
                        cid = request.data.get('club_id')
                        row.club_id = None if cid is None or str(cid).strip() == '' else str(cid).strip()
                        if row.club_id is None:
                            row.data_scope = DATA_SCOPE_ALL
                    row.granted_by = user.UserUID
                    row.save()
                    return Response({'code': 0, 'msg': '任职已更新'})

            if is_super:
                dedupe_active_assignments()
                qs = AdminAssignment.query.filter(status=1).order_by('yonghuid', '-is_primary', 'club_id')
            else:
                qs = AdminAssignment.query.filter(yonghuid=user.UserUID, status=1).order_by('-is_primary')

            return Response({
                'code': 0,
                'msg': 'ok',
                'data': {
                    'list': self._list_rows(qs.to_list()),
                    'can_manage': can_manage_admin_assignments(user, permissions),
                    'perm_note': (
                        '【功能权限】在「角色管理」里给账号绑 gvsdsdk 角色（perm_code 如 caiwu、002ab、000001）；'
                        '【数据范围】在本页维护 admin_assignment（能看哪个俱乐部/集团汇总）。'
                        '两套独立：集团高管需在本页配「集团全部」任职 + 角色管理里勾功能菜单。'
                    ),
                    'current_context': build_admin_club_context(user),
                },
            })
        except Exception:
            logger.exception('ClubAdminAssignmentListView 异常')
            return Response({'code': 99, 'msg': '任职数据操作失败，请确认已执行 migrate jituan'}, status=500)


class ClubManageView(APIView):
    """POST /jituan/houtai/club-manage — 俱乐部主数据与密钥配置（超级管理员）。"""
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    _EDITABLE_FIELDS = (
        'name', 'status', 'wx_appid', 'wx_secret',
        # 收款商户
        'mch_id', 'pay_app_id', 'mch_key', 'pay_cert_path', 'pay_key_path',
        # 小程序收款通道：wechat / fubei
        'mini_pay_channel',
        # 提现商户
        'withdraw_mch_id', 'api_v3_key',
        'cert_serial_no', 'private_key_path', 'platform_cert_dir',
        'official_appid', 'official_secret', 'official_token', 'encoding_aes_key',
        'template_id', 'template_max_per_minute', 'h5_domain', 'oss_prefix',
        'goeasy_appkey', 'goeasy_secret', 'sort_order',
    )

    @staticmethod
    def _effective_mch_key(club):
        cfg_json = club.config_json or {}
        return (
            (club.mch_key or '').strip()
            or (cfg_json.get('mch_key') or '').strip()
            or (cfg_json.get('shanghumiyao') or '').strip()
        )

    @staticmethod
    def _sync_mch_key_to_config_json(club, mch_key):
        cfg = dict(club.config_json or {})
        cfg['mch_key'] = (mch_key or '').strip()
        club.config_json = cfg

    def _pay_config_meta(self, club):
        """明确区分：小程序 V2 支付密钥 vs APIv3 密钥，避免后台误导。"""
        from jituan.services.wechat_pay import get_wechat_v2_config

        cfg_json = club.config_json or {}
        mch_key = self._effective_mch_key(club)
        api_v3 = (club.api_v3_key or '').strip()
        v2_cfg = get_wechat_v2_config(club.club_id)

        warnings = []
        if not mch_key:
            warnings.append(
                '未配置收款 APIv2 密钥 mch_key：点单/会员等收款不会使用提现 api_v3_key。'
            )
        if mch_key and api_v3 and mch_key == api_v3:
            warnings.append(
                '收款 mch_key 与提现 api_v3_key 相同：通常不正确，收款与提现是两套密钥。'
            )
        withdraw_mch = (getattr(club, 'withdraw_mch_id', None) or '').strip()
        pay_mch = (club.mch_id or '').strip()
        if withdraw_mch and pay_mch and withdraw_mch != pay_mch:
            # 这是预期场景，给信息而非警告
            pass
        elif not withdraw_mch and pay_mch:
            warnings.append(
                '提现商户号未单独填写：提现将回落收款商户号 mch_id。若两套商户不同请填写 withdraw_mch_id。'
            )
        wx_a = (club.wx_appid or '').strip()
        pay_a = (club.pay_app_id or '').strip()
        if wx_a and pay_a and wx_a != pay_a:
            warnings.append(
                f'wx_appid({wx_a}) 与 pay_app_id({pay_a}) 不一致：'
                '小程序登录/支付 openid 与 wx_appid 绑定，请保持一致或留空 pay_app_id。'
            )
        if not wx_a and pay_a:
            warnings.append(
                f'wx_appid 为空但 pay_app_id={pay_a}：登录仍可能回落旧全局 AppID，'
                '导致 openid 与支付 AppID 不匹配，请填写 wx_appid。'
            )

        return {
            'mch_key': mch_key,
            'mch_key_label': 'APIv2 密钥 (mch_key)',
            'mch_key_help': '微信商户平台 → API安全 → 设置APIv2密钥；用于会员/点单/押金/积分等 JSAPI 收款',
            'mch_key_configured': bool(mch_key),
            'miniapp_wechat_v2_key': mch_key,
            'miniapp_wechat_v2_key_configured': bool(mch_key),
            'miniapp_wechat_v2_key_label': 'APIv2 密钥 (mch_key)',
            'miniapp_wechat_v2_key_help': '微信商户平台 → API安全 → 设置APIv2密钥；用于会员/点单/押金/积分等 JSAPI 收款',
            'api_v3_key': api_v3,
            'api_v3_key_label': 'APIv3 密钥 (api_v3_key)',
            'api_v3_key_help': '微信商户平台 → API安全 → 设置APIv3密钥；仅用于自动提现/转账回调解密，不用于小程序收款',
            'effective_miniapp_pay': {
                'club_id': v2_cfg.get('club_id'),
                'appid': v2_cfg.get('appid'),
                'mch_id': v2_cfg.get('mch_id'),
                'key_len': len(v2_cfg.get('key') or ''),
                'key_source': v2_cfg.get('_key_source'),
            },
            'warnings': warnings,
        }

    def _serialize(self, club, full=False):
        from jituan.services.default_invite import get_club_default_dashou_invite
        from jituan.services.force_phone_auth import is_club_force_phone_auth

        cfg_json = club.config_json or {}
        base = {
            'club_id': club.club_id,
            'name': club.name,
            'status': club.status,
            'wx_appid': club.wx_appid,
            'h5_domain': club.h5_domain,
            'oss_prefix': club.oss_prefix,
            'mch_id': club.mch_id,
            'pay_app_id': club.pay_app_id,
            'withdraw_mch_id': getattr(club, 'withdraw_mch_id', '') or '',
            'mini_pay_channel': getattr(club, 'mini_pay_channel', None) or 'wechat',
            'goeasy_appkey': club.goeasy_appkey,
            'template_id': club.template_id,
            'sort_order': club.sort_order,
            'default_dashou_invite': get_club_default_dashou_invite(club.club_id),
            'force_phone_auth': is_club_force_phone_auth(club.club_id),
        }
        if full:
            mch_key = self._effective_mch_key(club)
            base.update({
                'wx_secret': club.wx_secret,
                'mch_key': mch_key,
                'pay_cert_path': getattr(club, 'pay_cert_path', '') or '',
                'pay_key_path': getattr(club, 'pay_key_path', '') or '',
                'api_v3_key': club.api_v3_key,
                'pay_config': self._pay_config_meta(club),
                'cert_serial_no': club.cert_serial_no,
                'private_key_path': club.private_key_path,
                'platform_cert_dir': club.platform_cert_dir,
                'official_appid': club.official_appid,
                'official_secret': club.official_secret,
                'official_token': club.official_token,
                'encoding_aes_key': club.encoding_aes_key,
                'goeasy_secret': club.goeasy_secret,
                'template_max_per_minute': club.template_max_per_minute,
                'default_dashou_invite_raw': (cfg_json.get('default_dashou_invite') or '').strip(),
            })
        return base

    def post(self, request):
        from jituan.constants import CLUB_ID_DEFAULT

        try:
            username = (request.data.get('phone') or request.data.get('username') or '').strip()
            if not username:
                return Response({'code': 401, 'msg': '缺少账号'}, status=401)
            kefu_obj, permissions = verify_kefu_permission(request, username)
            if kefu_obj is None:
                return permissions
            if not can_manage_admin_assignments(request.user):
                return Response({'code': 403, 'msg': '仅集团超管可管理俱乐部配置'}, status=403)

            action = (request.data.get('action') or 'get').strip()
            club_id = (request.data.get('club_id') or '').strip() or CLUB_ID_DEFAULT

            if action == 'list':
                clubs = Club.query.order_by('sort_order', 'club_id')
                return Response({
                    'code': 0,
                    'data': [self._serialize(c, full=False) for c in clubs],
                })

            club = Club.query.filter(club_id=club_id).first()
            if not club and action != 'create':
                return Response({'code': 1, 'msg': f'俱乐部 {club_id} 不存在'})

            if action == 'get':
                return Response({'code': 0, 'data': self._serialize(club, full=True)})

            if action == 'update':
                update_fields = []
                for field in self._EDITABLE_FIELDS:
                    if field in request.data:
                        val = request.data[field]
                        if field in ('mch_key', 'api_v3_key', 'wx_secret'):
                            val = (val or '').strip()
                        if field == 'mini_pay_channel':
                            val = (val or 'wechat').strip().lower()
                            if val not in ('wechat', 'fubei'):
                                return Response({'code': 400, 'msg': 'mini_pay_channel 仅支持 wechat/fubei'})
                        setattr(club, field, val)
                        update_fields.append(field)
                if 'mch_key' in request.data:
                    self._sync_mch_key_to_config_json(club, request.data.get('mch_key'))
                    if 'config_json' not in update_fields:
                        update_fields.append('config_json')
                if 'default_dashou_invite' in request.data:
                    from jituan.services.default_invite import set_club_default_dashou_invite
                    set_club_default_dashou_invite(club, request.data.get('default_dashou_invite'))
                    if 'config_json' not in update_fields:
                        update_fields.append('config_json')
                if 'force_phone_auth' in request.data:
                    from jituan.services.force_phone_auth import set_club_force_phone_auth
                    raw = request.data.get('force_phone_auth')
                    if isinstance(raw, str):
                        enabled = raw.strip().lower() in ('1', 'true', 'yes', 'on')
                    else:
                        enabled = bool(raw)
                    set_club_force_phone_auth(club, enabled)
                    if 'config_json' not in update_fields:
                        update_fields.append('config_json')
                if update_fields:
                    club.save(update_fields=update_fields)
                return Response({
                    'code': 0,
                    'msg': '保存成功',
                    'data': self._serialize(club, full=True),
                })

            if action == 'create':
                from jituan.services.club_bootstrap import bootstrap_club_from_template
                new_club_id = (request.data.get('new_club_id') or request.data.get('club_id') or '').strip()
                name = (request.data.get('name') or '').strip()
                if not new_club_id or not name:
                    return Response({'code': 400, 'msg': '新建俱乐部需要 new_club_id 与 name'})
                try:
                    club = bootstrap_club_from_template(
                        new_club_id,
                        name,
                        wx_appid=(request.data.get('wx_appid') or '').strip(),
                        template_club_id=(request.data.get('from_club') or CLUB_ID_DEFAULT).strip(),
                    )
                except ValueError as exc:
                    return Response({'code': 400, 'msg': str(exc)})
                return Response({
                    'code': 0,
                    'msg': '俱乐部创建成功',
                    'data': self._serialize(club, full=True),
                })

            return Response({'code': 400, 'msg': '无效 action，支持 list/get/update/create'})
        except Exception:
            logger.exception('ClubManageView 异常')
            return Response({'code': 99, 'msg': '俱乐部配置操作失败'}, status=500)


class ClubCertUploadView(APIView):
    """
    POST /jituan/houtai/club-cert-upload
    multipart: phone, club_id, kind, file
    kind: pay_key | pay_cert | withdraw_key | withdraw_cert | withdraw_platform
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [MultiPartParser, FormParser]

    def post(self, request):
        try:
            username = (request.data.get('phone') or request.data.get('username') or '').strip()
            if not username:
                return Response({'code': 401, 'msg': '缺少账号'}, status=401)
            kefu_obj, permissions = verify_kefu_permission(request, username)
            if kefu_obj is None:
                return permissions
            if not can_manage_admin_assignments(request.user):
                return Response({'code': 403, 'msg': '仅集团超管可上传商户证书'}, status=403)

            club_id = (request.data.get('club_id') or '').strip()
            kind = (request.data.get('kind') or '').strip()
            uploaded = request.FILES.get('file')
            if not club_id or not kind or not uploaded:
                return Response({'code': 400, 'msg': '需要 club_id、kind、file'})

            club = Club.query.filter(club_id=club_id).first()
            if not club:
                return Response({'code': 404, 'msg': f'俱乐部 {club_id} 不存在'})

            from jituan.services.club_cert_upload import save_club_cert_file, CERT_KINDS
            if kind not in CERT_KINDS:
                return Response({
                    'code': 400,
                    'msg': f'kind 无效，支持: {", ".join(CERT_KINDS.keys())}',
                })
            try:
                result = save_club_cert_file(club, kind, uploaded)
            except ValueError as exc:
                return Response({'code': 400, 'msg': str(exc)})
            except OSError as exc:
                logger.exception('证书落盘失败 club=%s kind=%s', club_id, kind)
                return Response({'code': 500, 'msg': f'写入失败: {exc}'})

            club = Club.query.filter(club_id=club_id).first()
            return Response({
                'code': 0,
                'msg': '上传成功',
                'data': {
                    **result,
                    'pay_cert_path': getattr(club, 'pay_cert_path', '') or '',
                    'pay_key_path': getattr(club, 'pay_key_path', '') or '',
                    'private_key_path': club.private_key_path or '',
                    'platform_cert_dir': club.platform_cert_dir or '',
                    'withdraw_mch_id': getattr(club, 'withdraw_mch_id', '') or '',
                    'mch_id': club.mch_id or '',
                },
            })
        except Exception:
            logger.exception('ClubCertUploadView 异常')
            return Response({'code': 99, 'msg': '证书上传失败'}, status=500)


class ClubCaiwuView(APIView):
    """
    俱乐部维度财务（新接口）。
    旧 POST /houtai/caiwu 完全不变；kefu 切换俱乐部后应调本接口。
    Header: X-Club-Id, 集团汇总 X-Club-Scope: all
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'caiwu' not in permissions:
            return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)
        try:
            data = build_caiwu_payload(request)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubCaiwuView 异常')
            return Response({'code': 99, 'msg': '统计失败', 'data': None}, status=500)


class ClubSzxxView(APIView):
    """
    俱乐部维度每日收支详细统计（新接口）。
    旧 POST /houtai/szxx 不变；参数与旧接口一致。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'caiwu' not in permissions:
            return Response({'code': 403, 'msg': '无财务查看权限'}, status=403)

        granularity = request.data.get('granularity', 'day')
        year = request.data.get('year')
        month = request.data.get('month')
        summary_date = request.data.get('summary_date')

        try:
            if year is not None:
                year = int(year)
            if month is not None:
                month = int(month)
        except (ValueError, TypeError):
            return Response({'code': 1, 'msg': '年份或月份格式错误'}, status=400)

        try:
            data, err = build_szxx_payload(request, granularity, year, month, summary_date)
            if err:
                return Response({'code': 1, 'msg': err}, status=400)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubSzxxView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class ClubAuditLogListView(APIView):
    """
    后台审计日志（admin_audit_log）。
    POST /jituan/houtai/audit-log
    权限：000001 或 czrz666（与操作日志一致）
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = (request.data.get('username') or request.data.get('phone') or '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少username'})

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '000001' not in permissions and 'czrz666' not in permissions:
            return Response({'code': 403, 'msg': '无权限查看审计日志'})

        try:
            page = int(request.data.get('page', 1))
        except (ValueError, TypeError):
            page = 1
        try:
            page_size = int(request.data.get('page_size', 20))
        except (ValueError, TypeError):
            page_size = 20

        data = list_admin_audit_logs(
            request,
            page=page,
            page_size=page_size,
            operator_yonghuid=request.data.get('operator_yonghuid'),
            target_yonghuid=request.data.get('target_yonghuid'),
            action=request.data.get('action'),
            keyword=request.data.get('keyword'),
        )
        return Response({'code': 0, 'msg': 'ok', 'data': data})


def _parse_year_month(data):
    year = data.get('year')
    month = data.get('month')
    try:
        if year is not None:
            year = int(year)
        if month is not None:
            month = int(month)
    except (ValueError, TypeError):
        return None, None, Response({'code': 1, 'msg': '年份或月份格式错误'}, status=400)
    return year, month, None


class ClubOrderTypesView(APIView):
    """POST /jituan/houtai/cwddhqlx — 订单财务商品类型筛选。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'dingdancaiwu' not in permissions:
            return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)
        return Response({'code': 0, 'msg': 'ok', 'data': build_order_types_payload()})


class ClubOrderFinanceView(APIView):
    """POST /jituan/houtai/cwhqjtddsj — 订单财务详细统计（按俱乐部）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'dingdancaiwu' not in permissions:
            return Response({'code': 403, 'msg': '无订单财务查看权限'}, status=403)

        year, month, err_resp = _parse_year_month(request.data)
        if err_resp:
            return err_resp
        try:
            data, err = build_order_finance_payload(
                request,
                request.data.get('granularity', 'day'),
                year,
                month,
                request.data.get('type_id', 0),
                request.data.get('order_source', 0),
                request.data.get('summary_date'),
            )
            if err:
                return Response({'code': 1, 'msg': err}, status=400)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubOrderFinanceView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class ClubChongzhiFinanceView(APIView):
    """POST /jituan/houtai/cwqtczhq — 其他充值/罚款财务统计（按俱乐部）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'qtczcaiwu' not in permissions:
            return Response({'code': 403, 'msg': '无其他充值财务查看权限'}, status=403)

        year, month, err_resp = _parse_year_month(request.data)
        if err_resp:
            return err_resp
        try:
            data, err = build_chongzhi_finance_payload(
                request,
                request.data.get('granularity', 'day'),
                year,
                month,
                request.data.get('types', [2, 3, 4, 5, 6]),
                request.data.get('summary_date'),
            )
            if err:
                return Response({'code': 1, 'msg': err}, status=400)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubChongzhiFinanceView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class ClubHuiyuanBankuaiView(APIView):
    """POST /jituan/houtai/cwhybkhq — 板块及会员列表（附带俱乐部售价）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'huiyuancaiwu' not in permissions:
            return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
        payload = build_huiyuan_bankuai_payload(request)
        return Response({
            'code': 0,
            'msg': 'ok',
            'data': payload['bankuai_list'],
            'club_id': payload['club_id'],
            'scope': payload['scope'],
        })


class ClubHuiyuanStatsView(APIView):
    """POST /jituan/houtai/hybkjtsj — 会员财务详细统计（按俱乐部）。"""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = request.data.get('phone', None)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        if 'huiyuancaiwu' not in permissions:
            return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)

        year, month, err_resp = _parse_year_month(request.data)
        if err_resp:
            return err_resp
        try:
            data, err = build_huiyuan_stats_payload(
                request,
                request.data.get('huiyuan_id'),
                request.data.get('granularity', 'day'),
                year,
                month,
                request.data.get('include_guanshi', False),
                request.data.get('include_zuzhang', False),
                request.data.get('summary_date'),
            )
            if err:
                return Response({'code': 1, 'msg': err}, status=400)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubHuiyuanStatsView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class ClubUserSummaryView(APIView):
    """
    用户管理汇总（打手/管事/商家/组长数量）。
    集团视图 X-Club-Scope: all 时额外返回 by_club 各公司拆分。
    POST /jituan/houtai/user-summary
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        username = (request.data.get('phone') or request.data.get('username') or '').strip()
        if not username:
            return Response({'code': 401, 'msg': '缺少账号'}, status=401)
        kefu_obj, permissions = verify_kefu_permission(request, username)
        if kefu_obj is None:
            return permissions
        try:
            data = build_user_summary(request)
            return Response({'code': 0, 'msg': 'ok', 'data': data})
        except Exception:
            logger.exception('ClubUserSummaryView 异常')
            return Response({'code': 99, 'msg': '统计失败'}, status=500)


class ClubDashouRegisterView(APIView):
    """
    打手邀请码注册（带俱乐部校验）。
    POST /jituan/auth/dashou-register  body: { inviteCode }
    与 /yonghu/dashouzhuce 逻辑一致，推荐新小程序使用。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        from users.views import DashouZhuceView
        return DashouZhuceView().post(request)
