import hmac
import threading
import traceback
import uuid
import hashlib
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_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
)
from users.business_models import User

## 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']

class GetOperationLogListView(APIView):
    """
    后台操作日志列表（xiugaijilu）
    POST /houtai/hqczrz
    权限：000001 超级权限 或 czrz666
    """
    permission_classes = []
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').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': '无权限查看操作日志（需要000001或czrz666）'})

        yonghuid = request.data.get('yonghuid', '').strip()
        xiugaiid = request.data.get('xiugaiid', '').strip()
        leixing = request.data.get('leixing')
        keyword = request.data.get('keyword', '').strip()
        try:
            page = max(1, int(request.data.get('page', 1)))
        except (ValueError, TypeError):
            page = 1
        try:
            page_size = min(100, max(1, int(request.data.get('page_size', 20))))
        except (ValueError, TypeError):
            page_size = 20

        qs = Xiugaijilu.query.all().order_by('-CreateTime')
        from jituan.services.admin_audit import filter_xiugaijilu_by_request
        qs = filter_xiugaijilu_by_request(qs, request)
        if yonghuid:
            qs = qs.filter(yonghuid=yonghuid)
        if xiugaiid:
            qs = qs.filter(xiugaiid__icontains=xiugaiid)
        if leixing not in (None, ''):
            try:
                qs = qs.filter(leixing=int(leixing))
            except (ValueError, TypeError):
                pass
        if keyword:
            qs = qs.filter(qitashuoming__icontains=keyword)

        total = qs.count()
        start = (page - 1) * page_size
        records = qs[start:start + page_size]

        def _fmt_decimal(val):
            if val is None:
                return None
            return str(val)

        data_list = []
        for r in records:
            data_list.append({
                'id': r.id,
                'yonghuid': r.yonghuid,
                'xiugaiid': r.xiugaiid,
                'leixing': r.leixing,
                'leixing_label': XIUGAI_LEIXING_LABEL.get(r.leixing, f'类型{r.leixing}'),
                'xiugaitijiao': _fmt_decimal(r.xiugaitijiao),
                'xiugaitijiaoq': _fmt_decimal(r.xiugaitijiaoq),
                'jifen': r.jifen,
                'yjifen': r.yjifen,
                'yajin': _fmt_decimal(r.yajin),
                'yyajin': _fmt_decimal(r.yyajin),
                'shangjiayue': _fmt_decimal(r.shangjiayue),
                'shangjiayueq': _fmt_decimal(r.shangjiayueq),
                'guanshiyue': _fmt_decimal(r.guanshiyue),
                'guanshiyueq': _fmt_decimal(r.guanshiyueq),
                'huiyuants': r.huiyuants,
                'huiyuan_id': r.huiyuan_id,
                'qitashuoming': r.qitashuoming or '',
                'create_time': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '',
            })

        from jituan.services.club_user_access import list_response_meta
        return Response({
            'code': 0,
            'data': {
                'list': data_list,
                'total': total,
                'page': page,
                'page_size': page_size,
                **list_response_meta(request),
            },
        })


class GetProductTypeZoneView(APIView):
    """
    获取商品类型、专区及会员列表
    权限：需要拥有 7007a
    请求：POST /houtai/hthqsplxzq
    参数：{"username": "客服手机号"}
    返回：
        code: 0
        data: {
            type_list: [{id, jieshao, tupian_url, yaoqiuleixing, huiyuan_id, yongjin, shenhezhuangtai, paixu, product_count}, ...],
            zone_list: [{id, mingzi, leixing_id, shenhezhuangtai, paixu}, ...],
            member_list: [{huiyuan_id, jieshao, jiage}, ...]
        }
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

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

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007a' not in permissions:
            return Response({'code': 403, 'msg': '您无权访问此页面'})

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

        club_scoped = resolve_club_scope(request) != DATA_SCOPE_ALL
        club_id = resolve_club_id_from_request(request)

        try:
            zone_qs_for_count = filter_club_char_field(
                ShangpinZhuanqu.query.all(), request, field='club_id'
            )
            zone_count_map = {
                row['leixing_id']: row['cnt']
                for row in zone_qs_for_count.values('leixing_id').annotate(cnt=Count('id'))
            }

            if club_scoped:
                # 具体俱乐部：仅本俱乐部已上架类型（与抢单端一致）
                admin_payload = build_club_leixing_admin_list(request)
                type_list = []
                for item in (admin_payload.get('list') or []):
                    type_list.append({
                        **item,
                        'product_count': zone_count_map.get(item['id'], 0),
                        'club_enabled': True,
                    })
            else:
                type_list = []
                for t in ShangpinLeixing.query.all().order_by('-paixu'):
                    type_list.append({
                        'id': t.id,
                        'jieshao': t.jieshao or '',
                        'tupian_url': t.tupian_url or '',
                        'yaoqiuleixing': t.yaoqiuleixing,
                        'huiyuan_id': t.huiyuan_id or '',
                        'yongjin': float(t.yongjin) if t.yongjin is not None else None,
                        'shenhezhuangtai': t.shenhezhuangtai,
                        'paixu': t.paixu,
                        'product_count': zone_count_map.get(t.id, 0),
                    })

            zones = filter_club_char_field(
                ShangpinZhuanqu.query.all().order_by('-paixu'), request, field='club_id'
            )
            zone_list = [
                {
                    'id': z.id,
                    'mingzi': z.mingzi or '',
                    'leixing_id': z.leixing_id,
                    'shenhezhuangtai': z.shenhezhuangtai,
                    'paixu': z.paixu,
                    'club_id': getattr(z, 'club_id', '') or '',
                }
                for z in zones
            ]

            member_list = [
                {
                    'huiyuan_id': m['huiyuan_id'],
                    'jieshao': m['jieshao'] or '',
                    'jiage': float(m['jiage'] or 0),
                }
                for m in Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage')
            ]

            return Response({
                'code': 0,
                'data': {
                    'type_list': type_list,
                    'zone_list': zone_list,
                    'member_list': member_list,
                    'club_scoped': club_scoped,
                    'club_id': club_id if club_scoped else '',
                }
            })
        except Exception:
            logging.getLogger(__name__).exception('GetProductTypeZoneView 失败')
            return Response({'code': 500, 'msg': '获取类型专区失败，请稍后重试'})




class ModifyProductTypeZoneView(APIView):
    """
    统一修改商品类型/专区（增、删、改）
    权限：需要拥有 7007a
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        obj_type = request.data.get('type')      # 'leixing' 或 'zhuanqu'
        action = request.data.get('action')      # 'add', 'update', 'delete'

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

        # 权限校验
        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007a' not in permissions:
            return Response({'code': 403, 'msg': '您无权进行此操作'})

        # 类型仍须集团汇总；专区允许在具体俱乐部操作
        if obj_type == 'leixing':
            scope_err = block_non_group_catalog_scope(request)
            if scope_err:
                return Response({'code': 400, 'msg': scope_err})
            return self.handle_leixing(request, action)
        elif obj_type == 'zhuanqu':
            return self.handle_zhuanqu(request, action)
        else:
            return Response({'code': 400, 'msg': '无效的type参数'})

    # ---------- 商品类型处理 ----------
    def handle_leixing(self, request, action):
        if action == 'add':
            return self.add_leixing(request)
        elif action == 'update':
            return self.update_leixing(request)
        elif action == 'delete':
            return self.delete_leixing(request)
        else:
            return Response({'code': 400, 'msg': '无效的action'})

    def add_leixing(self, request):
        # 获取并校验字段
        jieshao = request.data.get('jieshao', '').strip()
        if not jieshao:
            return Response({'code': 400, 'msg': '类型名称不能为空'})

        # 强制转为整数
        try:
            yaoqiuleixing = int(request.data.get('yaoqiuleixing', 0))
            if yaoqiuleixing not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '抢单要求类型无效（必须为1或2）'})

        try:
            shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
            if shenhezhuangtai not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
        yongjin = request.data.get('yongjin')
        if yongjin is not None and yongjin != '':
            try:
                yongjin = Decimal(str(yongjin))
            except:
                return Response({'code': 400, 'msg': '佣金金额格式错误'})
        else:
            yongjin = None

        # 图片校验
        image_file = request.FILES.get('file')
        if not image_file:
            return Response({'code': 400, 'msg': '请上传类型图片'})

        # 验证图片
        try:
            valid, msg = validate_image(image_file)
            if not valid:
                return Response({'code': 400, 'msg': msg})
        except ImportError:
            pass

        # 上传图片
        ext = image_file.name.split('.')[-1].lower()
        new_filename = f"{uuid.uuid4().hex}.{ext}"
        oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
        url = upload_to_oss(image_file, oss_path)
        if not url:
            return Response({'code': 500, 'msg': '图片上传失败'})

        # 创建类型
        try:
            with transaction.atomic():
                leixing = ShangpinLeixing.query.create(
                    jieshao=jieshao,
                    tupian_url=oss_path,
                    yaoqiuleixing=yaoqiuleixing,
                    huiyuan_id=huiyuan_id,
                    yongjin=yongjin,
                    shenhezhuangtai=shenhezhuangtai,
                    paixu=0
                )
            return Response({'code': 0, 'msg': '添加成功', 'data': {'id': leixing.id}})
        except Exception as e:
            logger.error(f"添加类型失败: {e}", exc_info=True)
            delete_from_oss(oss_path)
            return Response({'code': 500, 'msg': '数据库错误'})

    def update_leixing(self, request):
        type_id = request.data.get('id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少类型ID'})
        try:
            leixing = ShangpinLeixing.query.get(id=type_id)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 404, 'msg': '类型不存在'})

        changes = {}
        # 名称
        jieshao = request.data.get('jieshao')
        if jieshao is not None and jieshao.strip() != leixing.jieshao:
            changes['jieshao'] = jieshao.strip()

        # 抢单要求类型（强制转整数）
        yaoqiuleixing = request.data.get('yaoqiuleixing')
        if yaoqiuleixing is not None:
            try:
                new_val = int(yaoqiuleixing)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != leixing.yaoqiuleixing:
                    changes['yaoqiuleixing'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '抢单要求类型无效（必须为1或2）'})

        # 会员ID
        huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
        if huiyuan_id != leixing.huiyuan_id:
            changes['huiyuan_id'] = huiyuan_id

        # 佣金金额
        yongjin = request.data.get('yongjin')
        if yongjin is not None:
            if yongjin == '':
                new_val = None
            else:
                try:
                    new_val = Decimal(str(yongjin))
                except:
                    return Response({'code': 400, 'msg': '佣金金额格式错误'})
            if new_val != leixing.yongjin:
                changes['yongjin'] = new_val

        # 审核状态
        shenhezhuangtai = request.data.get('shenhezhuangtai')
        if shenhezhuangtai is not None:
            try:
                new_val = int(shenhezhuangtai)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != leixing.shenhezhuangtai:
                    changes['shenhezhuangtai'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        # 图片处理
        image_file = request.FILES.get('file')
        old_image_path = leixing.tupian_url
        if image_file:
            try:
                valid, msg = validate_image(image_file)
                if not valid:
                    return Response({'code': 400, 'msg': msg})
            except ImportError:
                pass
            ext = image_file.name.split('.')[-1].lower()
            new_filename = f"{uuid.uuid4().hex}.{ext}"
            oss_path = f"a_long/shangpin/shangpinleixing/{new_filename}"
            url = upload_to_oss(image_file, oss_path)
            if not url:
                return Response({'code': 500, 'msg': '图片上传失败'})
            changes['tupian_url'] = oss_path

        if not changes and not image_file:
            return Response({'code': 400, 'msg': '未做任何修改'})

        sync_to_products = request.data.get('sync_to_products') in [True, 'true', '1', 'on']
        try:
            with transaction.atomic():
                for field, value in changes.items():
                    setattr(leixing, field, value)
                leixing.save()

                # 同步商品
                if sync_to_products and ('yaoqiuleixing' in changes or 'huiyuan_id' in changes or 'yongjin' in changes):
                    product_updates = {}
                    if 'yaoqiuleixing' in changes:
                        product_updates['yaoqiuleixing'] = changes['yaoqiuleixing']
                    if 'huiyuan_id' in changes:
                        product_updates['huiyuan_id'] = changes['huiyuan_id']
                    if 'yongjin' in changes:
                        product_updates['yongjin'] = changes['yongjin']
                    if product_updates:
                        Shangpin.query.filter(leixing_id=type_id).update(**product_updates)

            # 删除旧图片
            if image_file and old_image_path:
                delete_from_oss(old_image_path)

            return Response({'code': 0, 'msg': '修改成功'})
        except Exception as e:
            logger.error(f"更新类型失败: {e}", exc_info=True)
            if image_file and 'oss_path' in locals():
                delete_from_oss(oss_path)
            return Response({'code': 500, 'msg': '修改失败'})

    def delete_leixing(self, request):
        type_id = request.data.get('id')
        if not type_id:
            return Response({'code': 400, 'msg': '缺少类型ID'})
        delete_products = request.data.get('delete_products') in [True, 'true', '1', 'on']

        try:
            leixing = ShangpinLeixing.query.get(id=type_id)
        except ShangpinLeixing.DoesNotExist:
            return Response({'code': 404, 'msg': '类型不存在'})

        with transaction.atomic():
            # 删除该类型下的所有专区
            zones = ShangpinZhuanqu.query.filter(leixing_id=type_id)
            if delete_products:
                # 收集 zone_ids 后批量删除，避免循环内 N+1 delete
                zone_ids = list(zones.values_list('id', flat=True))
                if zone_ids:
                    Shangpin.query.filter(zhuanqu_id__in=zone_ids).delete()
                    ShangpinZhuanqu.query.filter(id__in=zone_ids).delete()
                # 删除直接关联该类型的商品
                Shangpin.query.filter(leixing_id=type_id).delete()
            else:
                zones.delete()
                # 仅清除商品的类型关联
                Shangpin.query.filter(leixing_id=type_id).update(leixing_id=None)

            leixing.delete()

        if leixing.tupian_url:
            delete_from_oss(leixing.tupian_url)

        return Response({'code': 0, 'msg': '删除成功'})

    # ---------- 商品专区处理 ----------
    def handle_zhuanqu(self, request, action):
        if action == 'add':
            return self.add_zhuanqu(request)
        elif action == 'update':
            return self.update_zhuanqu(request)
        elif action == 'delete':
            return self.delete_zhuanqu(request)
        else:
            return Response({'code': 400, 'msg': '无效的action'})

    def _upload_zhuanqu_icon(self, image_file):
        """专区图标上传到 OSS，相对路径前缀 shangpin/zhuanqu/（按俱乐部存库，路径共用前缀）。"""
        try:
            valid, msg = validate_image(image_file)
            if not valid:
                return None, msg
        except ImportError:
            pass
        ext = image_file.name.split('.')[-1].lower() if image_file.name and '.' in image_file.name else 'png'
        if ext not in ('jpg', 'jpeg', 'png', 'gif', 'webp'):
            ext = 'png'
        new_filename = f"{int(time.time())}_{uuid.uuid4().hex[:12]}.{ext}"
        oss_path = f"shangpin/zhuanqu/{new_filename}"
        url = upload_to_oss(image_file, oss_path)
        if not url:
            return None, '图片上传失败'
        return oss_path, None

    def add_zhuanqu(self, request):
        mingzi = request.data.get('mingzi', '').strip()
        leixing_id = request.data.get('leixing_id')
        if not mingzi:
            return Response({'code': 400, 'msg': '专区名称不能为空'})
        if not leixing_id:
            return Response({'code': 400, 'msg': '请选择所属类型'})

        try:
            leixing_id = int(leixing_id)
            if not ShangpinLeixing.query.filter(id=leixing_id).exists():
                return Response({'code': 404, 'msg': '所属类型不存在'})
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '所属类型ID无效'})

        try:
            shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
            if shenhezhuangtai not in [1, 2]:
                raise ValueError
        except (TypeError, ValueError):
            return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        # 图标：优先上传文件；否则可选手填相对路径（兼容旧用法）
        tupian_url = (request.data.get('tupian_url') or '').strip()
        image_file = request.FILES.get('file')
        uploaded_path = None
        if image_file:
            uploaded_path, err = self._upload_zhuanqu_icon(image_file)
            if err:
                return Response({'code': 500 if err == '图片上传失败' else 400, 'msg': err})
            tupian_url = uploaded_path

        try:
            with transaction.atomic():
                from jituan.services.club_write import resolve_club_id_for_write
                club_id = resolve_club_id_for_write(request)
                zhuanqu = ShangpinZhuanqu.query.create(
                    mingzi=mingzi,
                    leixing_id=leixing_id,
                    shenhezhuangtai=shenhezhuangtai,
                    paixu=0,
                    club_id=club_id,
                    tupian_url=tupian_url,
                )
            return Response({
                'code': 0,
                'msg': '添加成功',
                'data': {'id': zhuanqu.id, 'club_id': club_id, 'tupian_url': tupian_url},
            })
        except Exception as e:
            logger.error(f"添加专区失败: {e}", exc_info=True)
            if uploaded_path:
                delete_from_oss(uploaded_path)
            return Response({'code': 500, 'msg': '数据库错误'})

    def update_zhuanqu(self, request):
        zone_id = request.data.get('id')
        if not zone_id:
            return Response({'code': 400, 'msg': '缺少专区ID'})
        try:
            zhuanqu = ShangpinZhuanqu.query.get(id=zone_id)
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})

        from jituan.constants import DATA_SCOPE_ALL
        from jituan.services.club_context import resolve_club_scope
        from jituan.services.club_write import resolve_club_id_for_write
        if resolve_club_scope(request) != DATA_SCOPE_ALL:
            write_club = resolve_club_id_for_write(request)
            zone_club = getattr(zhuanqu, 'club_id', None) or 'xq'
            if zone_club != write_club:
                return Response({'code': 403, 'msg': '专区不属于当前俱乐部'})

        changes = {}
        mingzi = request.data.get('mingzi')
        if mingzi is not None and mingzi.strip() != zhuanqu.mingzi:
            changes['mingzi'] = mingzi.strip()

        leixing_id = request.data.get('leixing_id')
        if leixing_id is not None:
            try:
                new_id = int(leixing_id)
                if not ShangpinLeixing.query.filter(id=new_id).exists():
                    return Response({'code': 404, 'msg': '新所属类型不存在'})
                if new_id != zhuanqu.leixing_id:
                    changes['leixing_id'] = new_id
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '所属类型ID无效'})

        shenhezhuangtai = request.data.get('shenhezhuangtai')
        if shenhezhuangtai is not None:
            try:
                new_val = int(shenhezhuangtai)
                if new_val not in [1, 2]:
                    raise ValueError
                if new_val != zhuanqu.shenhezhuangtai:
                    changes['shenhezhuangtai'] = new_val
            except (TypeError, ValueError):
                return Response({'code': 400, 'msg': '审核状态无效（必须为1或2）'})

        old_image_path = getattr(zhuanqu, 'tupian_url', None) or ''
        image_file = request.FILES.get('file')
        uploaded_path = None
        if image_file:
            uploaded_path, err = self._upload_zhuanqu_icon(image_file)
            if err:
                return Response({'code': 500 if err == '图片上传失败' else 400, 'msg': err})
            if uploaded_path != old_image_path:
                changes['tupian_url'] = uploaded_path
        elif 'tupian_url' in request.data:
            new_url = (request.data.get('tupian_url') or '').strip()
            if new_url != old_image_path:
                changes['tupian_url'] = new_url

        if not changes:
            return Response({'code': 400, 'msg': '未做任何修改'})

        try:
            with transaction.atomic():
                for field, value in changes.items():
                    setattr(zhuanqu, field, value)
                zhuanqu.save()
            # 换图或清空后删旧 OSS（新上传路径与旧不同时）
            if 'tupian_url' in changes and old_image_path and old_image_path != changes['tupian_url']:
                delete_from_oss(old_image_path)
            return Response({
                'code': 0,
                'msg': '修改成功',
                'data': {'id': zhuanqu.id, 'tupian_url': getattr(zhuanqu, 'tupian_url', '') or ''},
            })
        except Exception as e:
            logger.error(f"更新专区失败: {e}", exc_info=True)
            if uploaded_path:
                delete_from_oss(uploaded_path)
            return Response({'code': 500, 'msg': '修改失败'})

    def delete_zhuanqu(self, request):
        zone_id = request.data.get('id')
        if not zone_id:
            return Response({'code': 400, 'msg': '缺少专区ID'})
        delete_products = request.data.get('delete_products') in [True, 'true', '1', 'on']

        try:
            zhuanqu = ShangpinZhuanqu.query.get(id=zone_id)
        except ShangpinZhuanqu.DoesNotExist:
            return Response({'code': 404, 'msg': '专区不存在'})

        from jituan.constants import DATA_SCOPE_ALL
        from jituan.services.club_context import resolve_club_scope
        from jituan.services.club_write import resolve_club_id_for_write
        if resolve_club_scope(request) != DATA_SCOPE_ALL:
            write_club = resolve_club_id_for_write(request)
            zone_club = getattr(zhuanqu, 'club_id', None) or 'xq'
            if zone_club != write_club:
                return Response({'code': 403, 'msg': '专区不属于当前俱乐部'})

        old_image_path = getattr(zhuanqu, 'tupian_url', None) or ''
        with transaction.atomic():
            if delete_products:
                Shangpin.query.filter(zhuanqu_id=zone_id).delete()
            else:
                Shangpin.query.filter(zhuanqu_id=zone_id).update(zhuanqu_id=None)
            zhuanqu.delete()

        if old_image_path:
            delete_from_oss(old_image_path)

        return Response({'code': 0, 'msg': '删除成功'})


# 继续在 houtai/views_product.py 中添加
class GetRateView(APIView):
    """
    获取打手分成费率（平台订单和商家订单）
    权限：需要拥有 7007b
    请求：POST /houtai/hthqfhpz
    参数：{"username": "客服手机号"}
    返回：{"code":0, "data": {"platform_rate": xx, "merchant_rate": xx}}
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

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

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007b' not in permissions:
            return Response({'code': 403, 'msg': '您无权查看费率'})

        try:
            platform_rate_obj = CommissionRate.query.get(Platform='1')
            merchant_rate_obj = CommissionRate.query.get(Platform='3')
        except CommissionRate.DoesNotExist as e:
            logger.error(f"费率记录缺失: {e}")
            return Response({'code': 500, 'msg': '费率配置缺失'})

        return Response({
            'code': 0,
            'data': {
                'platform_rate': float(platform_rate_obj.Rate) if platform_rate_obj.Rate else 0,
                'merchant_rate': float(merchant_rate_obj.Rate) if merchant_rate_obj.Rate else 0,
            }
        })


class ModifyRateView(APIView):
    """
    修改打手分成费率
    权限：需要拥有 7007b
    请求：POST /houtai/htxgfl
    参数：{"username": "客服手机号", "type": "platform" / "merchant", "value": 费率(百分比数字)}
    返回：{"code":0, "msg":"修改成功"}
    """
    permission_classes = [IsAuthenticated]
    parser_classes = [JSONParser]

    def post(self, request):
        username = request.data.get('username', '').strip()
        rate_type = request.data.get('type')
        value = request.data.get('value')

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

        kefu, permissions = verify_kefu_permission(request, username)
        if kefu is None:
            return permissions
        if '7007b' not in permissions:
            return Response({'code': 403, 'msg': '您无权修改费率'})

        try:
            rate_value = Decimal(str(value))
        except:
            return Response({'code': 400, 'msg': '费率值格式错误'})


        if rate_type == 'platform':
            Platform = '1'
        elif rate_type == 'merchant':
            Platform = '3'
        else:
            return Response({'code': 400, 'msg': '无效的type参数'})

        try:
            with transaction.atomic():
                rate_obj = CommissionRate.objects.select_for_update().get(Platform=Platform)
                rate_obj.Rate = rate_value
                rate_obj.save()
        except CommissionRate.DoesNotExist:
            return Response({'code': 404, 'msg': '费率记录不存在'})
        except Exception as e:
            logger.error(f"修改费率失败: {e}", exc_info=True)
            return Response({'code': 500, 'msg': '修改失败'})

        return Response({'code': 0, 'msg': '修改成功'})






# 权限码（根据你要求，使用 '8080a'）
REQUIRED_PERMISSION = '8080a'
class PopupNoticeListAPIView(APIView):
    """
    获取所有弹窗配置（页面 + 弹窗 + 图片）
    URL: POST /houtai/hthqtcxx
    请求参数: { "username": "客服账号" }
    响应格式: { "code": 0, "data": { "pages": [...] } }
    注意：所有响应均使用默认 HTTP 200 状态码，业务成功/失败通过 code 字段区分
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # 1. 获取前端传递的 username（用于防越权）
        username_frontend = request.data.get('username')
        # 2. 调用公共权限验证方法，返回 (客服对象, 权限列表)
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            # 身份验证失败，返回业务码 403，但 HTTP 状态码仍是 200
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})

        # 3. 检查是否拥有所需权限码 '8080a'
        if REQUIRED_PERMISSION not in permissions:
            return Response({'code': 403, 'msg': '您没有权限访问弹窗管理功能'})

        # 4. 查询所有页面，并预加载关联的弹窗和图片（避免N+1查询）
        from jituan.services.display_config import filter_popup_page_by_request, list_response_meta

        pages = filter_popup_page_by_request(
            PopupPage.query.all().prefetch_related('popups__images'),
            request,
        ).order_by('id')

        # 5. 序列化输出
        serializer = PopupPageSerializer(pages, many=True)
        return Response({
            'code': 0,
            'data': {'pages': serializer.data, **list_response_meta(request)},
        })


class PopupNoticeModifyAPIView(APIView):
    """
    统一修改接口：支持页面、弹窗、图片的增删改
    URL: POST /houtai/htxgtcxx
    请求中必须包含 action 字段，用于区分操作类型。
    所有响应均使用默认 HTTP 200 状态码，业务成功/失败通过 code 字段体现。
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        # ---------- 1. 权限验证 ----------
        username_frontend = request.data.get('username')
        kefu, permissions = verify_kefu_permission(request, username_frontend)
        if kefu is None:
            return Response({'code': 403, 'msg': '身份验证失败，请检查登录状态'})
        if REQUIRED_PERMISSION not in permissions:
            return Response({'code': 403, 'msg': '您没有权限操作弹窗管理'})

        from jituan.services.display_config import forbid_display_write_in_all_scope
        deny = forbid_display_write_in_all_scope(request)
        if deny:
            return deny

        # ---------- 2. 获取 action 并分发 ----------
        action = request.data.get('action')
        if not action:
            return Response({'code': 400, 'msg': '缺少action参数'})

        try:
            if action == 'add_page':
                return self.add_page(request)
            elif action == 'update_page':
                return self.update_page(request)
            elif action == 'delete_page':
                return self.delete_page(request)
            elif action == 'add_popup':
                return self.add_popup(request)
            elif action == 'update_popup':
                return self.update_popup(request)
            elif action == 'delete_popup':
                return self.delete_popup(request)
            elif action == 'add_image':
                return self.add_image(request)
            elif action == 'update_image':
                return self.update_image(request)
            elif action == 'delete_image':
                return self.delete_image(request)
            else:
                return Response({'code': 400, 'msg': f'未知的action: {action}'})
        except Exception as e:
            # 捕获未预期的异常，返回服务器错误
            traceback.print_exc()
            return Response({'code': 500, 'msg': f'服务器错误: {str(e)}'})

    # ==================== 页面相关操作 ====================

    def add_page(self, request):
        """添加新页面"""
        from jituan.services.club_context import resolve_club_id_from_request
        from jituan.services.display_config import forbid_display_write_in_all_scope

        deny = forbid_display_write_in_all_scope(request)
        if deny:
            return deny

        data = request.data
        # 必填字段校验
        if not data.get('page_key') or not data.get('name'):
            return Response({'code': 400, 'msg': '缺少 page_key 或 name'})
        club_id = resolve_club_id_from_request(request)
        # 唯一性校验（同俱乐部内）
        if PopupPage.query.filter(club_id=club_id, page_key=data['page_key']).exists():
            return Response({'code': 400, 'msg': '页面标识已存在'})

        page = PopupPage.query.create(
            club_id=club_id,
            page_key=data['page_key'],
            name=data['name'],
            description=data.get('description', ''),
            is_active=data.get('is_active', True),
            ignore_user_mute=data.get('ignore_user_mute', False)
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': page.id}})

    def update_page(self, request):
        """更新页面信息"""
        page_id = request.data.get('id')
        if not page_id:
            return Response({'code': 400, 'msg': '缺少页面id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        # 更新允许修改的字段
        page.page_key = request.data.get('page_key', page.page_key)
        page.name = request.data.get('name', page.name)
        page.description = request.data.get('description', page.description)
        page.is_active = request.data.get('is_active', page.is_active)
        page.ignore_user_mute = request.data.get('ignore_user_mute', page.ignore_user_mute)
        page.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_page(self, request):
        """删除页面，可级联删除其下所有弹窗和图片"""
        page_id = request.data.get('id')
        cascade = request.data.get('cascade_delete_popups', True)  # 默认级联
        if not page_id:
            return Response({'code': 400, 'msg': '缺少页面id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope, forbid_display_write_in_all_scope
        deny = forbid_display_write_in_all_scope(request) or forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        if cascade:
            # 级联删除：使用事务保证原子性
            with transaction.atomic():
                # 预加载 images 关联，避免循环内 N+1 访问 related manager
                popups = page.popups.prefetch_related('images')
                for popup in popups:
                    # 删除弹窗下的所有图片文件
                    for img in popup.images.all():
                        if img.image_url:
                            delete_from_oss(img.image_url)
                popups.delete()
                page.delete()
        else:
            # 非级联删除：如果页面下还有弹窗，则拒绝删除
            if page.popups.exists():
                return Response({'code': 400, 'msg': '该页面下存在弹窗，请先删除弹窗或使用级联删除'})
            page.delete()

        return Response({'code': 0, 'msg': '删除成功'})

    # ==================== 弹窗相关操作 ====================

    def add_popup(self, request):
        """为指定页面添加新弹窗"""
        data = request.data
        page_id = data.get('page_id')
        popup_id = data.get('popup_id')
        if not page_id or not popup_id:
            return Response({'code': 400, 'msg': '缺少 page_id 或 popup_id'})
        try:
            page = PopupPage.query.get(id=page_id)
        except PopupPage.DoesNotExist:
            return Response({'code': 404, 'msg': '页面不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, page)
        if deny:
            return deny

        # 同一页面下 popup_id 必须唯一
        if PopupConfig.query.filter(page=page, popup_id=popup_id).exists():
            return Response({'code': 400, 'msg': '该页面下已存在相同的弹窗ID'})

        popup = PopupConfig.query.create(
            page=page,
            popup_id=popup_id,
            title=data.get('title', ''),
            description=data.get('description', ''),
            strategy_type=data.get('strategy_type', 'daily'),
            max_count=data.get('max_count', 1),
            reset_interval=data.get('reset_interval', 'day'),
            duration=data.get('duration', 0),
            force_even_muted=data.get('force_even_muted', False),
            sort_order=data.get('sort_order', 0),
            is_active=data.get('is_active', True),
            start_time=data.get('start_time') or None,
            end_time=data.get('end_time') or None
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': popup.id}})

    def update_popup(self, request):
        """更新弹窗信息"""
        popup_id = request.data.get('id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少弹窗id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        # 更新弹窗字段
        popup.popup_id = request.data.get('popup_id', popup.popup_id)
        popup.title = request.data.get('title', popup.title)
        popup.description = request.data.get('description', popup.description)
        popup.strategy_type = request.data.get('strategy_type', popup.strategy_type)
        popup.max_count = request.data.get('max_count', popup.max_count)
        popup.reset_interval = request.data.get('reset_interval', popup.reset_interval)
        popup.duration = request.data.get('duration', popup.duration)
        popup.force_even_muted = request.data.get('force_even_muted', popup.force_even_muted)
        popup.sort_order = request.data.get('sort_order', popup.sort_order)
        popup.is_active = request.data.get('is_active', popup.is_active)
        popup.start_time = request.data.get('start_time') or None
        popup.end_time = request.data.get('end_time') or None
        popup.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_popup(self, request):
        """删除弹窗，同时删除其下所有图片（包括OSS文件）"""
        popup_id = request.data.get('id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少弹窗id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        with transaction.atomic():
            for img in popup.images.all():
                if img.image_url:
                    delete_from_oss(img.image_url)
            popup.images.all().delete()
            popup.delete()
        return Response({'code': 0, 'msg': '删除成功'})

    # ==================== 图片相关操作 ====================

    def _upload_image_file(self, file_obj, page_key):
        """
        内部方法：将图片上传到OSS指定目录
        目录: a_long/tanchuang/tanchuang/
        文件名: {page_key}_{时间戳}_{uuid8}.{ext}
        返回: 相对路径（例如 a_long/tanchuang/tanchuang/dashouzhongxin_20250419120000_abc12345.jpg）
        """
        # 验证图片格式和大小
        valid, msg = validate_image(file_obj)
        if not valid:
            raise ValueError(msg)

        # 生成唯一文件名
        ext = os.path.splitext(file_obj.name)[1].lower()
        timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
        unique_id = str(uuid.uuid4())[:8]
        filename = f"{page_key}_{timestamp}_{unique_id}{ext}"
        relative_path = f"a_long/tanchuang/tanchuang/{filename}"

        # 上传到OSS
        url = upload_to_oss(file_obj, relative_path)
        if not url:
            raise ValueError("图片上传失败")
        return relative_path

    def add_image(self, request):
        """为指定弹窗添加图片（支持上传文件）"""
        popup_id = request.data.get('popup_id')
        if not popup_id:
            return Response({'code': 400, 'msg': '缺少 popup_id'})
        try:
            popup = PopupConfig.query.get(id=popup_id)
        except PopupConfig.DoesNotExist:
            return Response({'code': 404, 'msg': '弹窗不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, popup.page)
        if deny:
            return deny

        # 获取上传的文件
        file_obj = request.FILES.get('file')
        if not file_obj:
            return Response({'code': 400, 'msg': '未提供图片文件'})

        try:
            page_key = popup.page.page_key
            relative_path = self._upload_image_file(file_obj, page_key)
        except ValueError as e:
            return Response({'code': 400, 'msg': str(e)})

        # 保存图片记录
        image = PopupImage.query.create(
            popup_config=popup,
            image_url=relative_path,
            text=request.data.get('text', ''),
            sort_order=request.data.get('sort_order', 0)
        )
        return Response({'code': 0, 'msg': '添加成功', 'data': {'id': image.id}})

    def update_image(self, request):
        """更新图片信息（可替换图片文件、修改文字、排序）"""
        image_id = request.data.get('id')
        if not image_id:
            return Response({'code': 400, 'msg': '缺少图片id'})
        try:
            image = PopupImage.query.get(id=image_id)
        except PopupImage.DoesNotExist:
            return Response({'code': 404, 'msg': '图片不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, image.popup_config.page)
        if deny:
            return deny

        # 更新文字和排序
        image.text = request.data.get('text', image.text)
        image.sort_order = request.data.get('sort_order', image.sort_order)

        # 如果提供了新图片文件，则替换
        file_obj = request.FILES.get('file')
        if file_obj:
            try:
                page_key = image.popup_config.page.page_key
                new_path = self._upload_image_file(file_obj, page_key)
            except ValueError as e:
                return Response({'code': 400, 'msg': str(e)})
            # 删除旧图片
            if image.image_url:
                delete_from_oss(image.image_url)
            image.image_url = new_path

        image.save()
        return Response({'code': 0, 'msg': '更新成功'})

    def delete_image(self, request):
        """删除图片（同时删除OSS文件）"""
        image_id = request.data.get('id')
        if not image_id:
            return Response({'code': 400, 'msg': '缺少图片id'})
        try:
            image = PopupImage.query.get(id=image_id)
        except PopupImage.DoesNotExist:
            return Response({'code': 404, 'msg': '图片不存在'})

        from jituan.services.display_config import forbid_popup_page_out_of_scope
        deny = forbid_popup_page_out_of_scope(request, image.popup_config.page)
        if deny:
            return deny

        if image.image_url:
            delete_from_oss(image.image_url)
        image.delete()
        return Response({'code': 0, 'msg': '删除成功'})



