import io
import os
import re
import time
import random
import secrets
import hashlib
import threading
import urllib.parse
from decimal import Decimal, InvalidOperation

from django.conf import settings
from django.db import transaction, connection
from django.db.models import Q, F, Max, Prefetch
from gvsdsdk.fluent import db, func, FQ
from django.core.cache import cache
from django.core.paginator import Paginator
from django.utils import timezone

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

from utils.oss_utils import upload_to_oss, delete_from_oss, validate_image
from utils.weixin_broadcast import WeixinBroadcastSender
from utils.weixin_token import get_weixin_mini_access_token, is_weixin_token_invalid
# from utils.ip_security import *
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode

from backend.utils import update_shangjia_daily
from utils.chat_utils import subscribe_merchant_link_chat
from utils.pdd_order_validator import validate_pdd_order_id, validate_cn_mobile

from ..models import (
    Gonggao, Lunbo, Tupianpeizhi, Qunpeizhi,
    ShangjiaMoban, ShangjiaLianjie, PopupPage, PopupConfig, WithdrawConfig,
    MiniappScriptScene, MiniappScriptAutoReply,
)

from users.models import (
    AdminProfile, UserDashou, UserBoss,
    UserShangjia, UserGuanshi, UserZuzhang
)
from users.business_models import User

from orders.models import CommissionRate, Order, MerchantOrderExt
from orders.notice_tasks import dingdan_guangbo

from rank.models import Chenghao, DingdanBiaoqian

from products.models import ShangpinLeixing, Huiyuangoumai

from ..serializers import PopupConfigSerializer
import traceback
import requests

import logging
logger = logging.getLogger(__name__)

class GuanZhuAListView(APIView):
    """
    获取关注阿龙页面图片列表
    POST /peizhi/gzal
    需要JWT认证
    返回：
    {
        "code": 0,
        "msg": "success",
        "data": {
            "images": ["相对URL1", "相对URL2", ...],
            "last_update": "2025-03-16 12:34:56",
            "visit_count": 123
        }
    }
    """
    permission_classes = [IsAuthenticated]

    def post(self, request):
        try:
            from jituan.services.club_context import resolve_effective_club_id

            club_id = resolve_effective_club_id(request, request.user)
            qs = Tupianpeizhi.query.filter(ImageType=3, club_id=club_id)
            if not qs.exists():
                return Response({
                    'code': 0,
                    'msg': 'success',
                    'data': {
                        'images': [],
                        'last_update': None,
                        'visit_count': 0
                    }
                }, status=status.HTTP_200_OK)

            # 获取所有图片的相对URL
            images = list(qs.values_list('ImageURL', flat=True))

            # 获取最新更新时间（取所有记录的UpdateTime最大值）
            last_update = qs.aggregate(UpdateTime__max=Max('UpdateTime'))['UpdateTime__max']
            last_update_str = last_update.strftime('%Y-%m-%d %H:%M:%S') if last_update else None

            # 原子性增加访问次数
            # 先更新所有记录，再获取更新后的最大次数
            qs.update(AccessCount=F('AccessCount') + 1)
            visit_count = qs.aggregate(access_count__max=Max('AccessCount'))['access_count__max'] or 0

            return Response({
                'code': 0,
                'msg': 'success',
                'data': {
                    'images': images,
                    'last_update': last_update_str,
                    'visit_count': visit_count
                }
            }, status=status.HTTP_200_OK)

        except Exception as e:
            logger.error(f"获取关注阿龙图片列表失败: {e}", exc_info=True)
            return Response({
                'code': 1,
                'msg': '服务器内部错误',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)






#不用缓存，一天2000次
class GuanshiQRCodeView(APIView):
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        logger.info("===== 开始生成管事二维码 =====")
        current_user = request.user
        logger.info(f"当前用户ID: {current_user.UserUID}")

        try:
            from jituan.services.club_context import resolve_effective_club_id
            from utils.weixin_token import get_club_mini_access_token, is_weixin_token_invalid

            club_id = resolve_effective_club_id(request, current_user)
            try:
                guanshi = current_user.GuanshiProfile
                logger.info(f"管事扩展表查询成功，状态: {guanshi.zhuangtai}")
            except UserGuanshi.DoesNotExist:
                logger.warning(f"用户 {current_user.UserUID} 不是管事身份")
                return Response(
                    {'code': 403, 'message': '当前用户不是管事身份', 'data': None},
                    status=status.HTTP_403_FORBIDDEN
                )

            if guanshi.zhuangtai != 1:
                logger.warning(f"用户 {current_user.UserUID} 管事状态异常: {guanshi.zhuangtai}")
                return Response(
                    {'code': 403, 'message': '管事账号状态异常', 'data': None},
                    status=status.HTTP_403_FORBIDDEN
                )

            invite_code = guanshi.yaoqingma
            if not invite_code:
                logger.info(f"用户 {current_user.UserUID} 无邀请码，开始生成")
                invite_code = CreateInvitationCode(str(current_user.UserUID))
                if not VerifyInvitationCode(invite_code):
                    logger.error(f"邀请码唯一性校验失败，用户: {current_user.UserUID}")
                    return Response(
                        {'code': 500, 'message': '邀请码生成失败', 'data': None},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR
                    )
                guanshi.yaoqingma = invite_code
                guanshi.save(update_fields=['yaoqingma'])
                logger.info(f"邀请码生成成功: {invite_code}")
            else:
                logger.info(f"使用已有邀请码: {invite_code}")

            # 按俱乐部使用对应小程序 appid 的 access_token
            access_token = get_club_mini_access_token(club_id)
            if not access_token:
                logger.error("获取 access_token 失败 club_id=%s", club_id)
                return Response(
                    {'code': 500, 'message': '获取微信access_token失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )
            logger.info(f"获取到 access_token (前10位): {access_token[:10]}... club={club_id}")

            encoded_invite = urllib.parse.quote(invite_code, safe='')
            # 新小程序：打手端 Tab「我的」；旧码 dashouduan 由小程序兼容页转发
            page_path = f'pages/fighter/fighter?inviteCode={encoded_invite}'
            wx_url = f'https://api.weixin.qq.com/wxa/getwxacode?access_token={access_token}'
            post_data = {
                'path': page_path,
                'width': 430,
                'auto_color': False,
                'line_color': {'r': 0, 'g': 0, 'b': 0}
            }
            logger.info(f"调用微信接口生成小程序码，path: {page_path}")

            def _request_qr(token):
                try:
                    return requests.post(
                        f'https://api.weixin.qq.com/wxa/getwxacode?access_token={token}',
                        json=post_data, timeout=10,
                    )
                except Exception as e:
                    logger.error(f"请求微信接口异常: {str(e)}", exc_info=True)
                    return None

            wx_resp = _request_qr(access_token)
            if wx_resp is None:
                return Response(
                    {'code': 500, 'message': '调用微信服务失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            if wx_resp.headers.get('content-type', '').startswith('application/json'):
                try:
                    err = wx_resp.json()
                    if is_weixin_token_invalid(err.get('errcode'), err.get('errmsg', '')):
                        access_token = get_club_mini_access_token(club_id, force_refresh=True)
                        if access_token:
                            wx_resp = _request_qr(access_token)
                except Exception:
                    pass

            logger.info(f"微信接口响应状态码: {wx_resp.status_code}")

            if wx_resp.status_code != 200:
                error_info = {}
                if wx_resp.headers.get('content-type') == 'application/json':
                    try:
                        error_info = wx_resp.json()
                    except:
                        pass
                errmsg = error_info.get('errmsg', '未知错误')
                logger.error(f"微信接口返回错误 (HTTP {wx_resp.status_code}): {errmsg}")
                if 'errcode' in error_info:
                    logger.error(f"微信错误码: {error_info.get('errcode')}")
                return Response(
                    {'code': 500, 'message': f'生成二维码失败: {errmsg}', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            content_type = wx_resp.headers.get('content-type', '')
            if 'image' not in content_type:
                try:
                    error_info = wx_resp.json()
                    errmsg = error_info.get('errmsg', '未知错误')
                    logger.error(f"微信接口返回非图片内容: {errmsg}")
                    return Response(
                        {'code': 500, 'message': f'生成二维码失败: {errmsg}', 'data': None},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR
                    )
                except:
                    logger.error("微信接口返回未知内容，无法解析为JSON")
                    return Response(
                        {'code': 500, 'message': '生成二维码失败', 'data': None},
                        status=status.HTTP_500_INTERNAL_SERVER_ERROR
                    )

            timestamp = int(time.time())
            filename = f"{current_user.UserUID}_{timestamp}.png"
            oss_path = f"erweima/guanshi/{club_id}/{filename}"
            file_obj = io.BytesIO(wx_resp.content)
            logger.info(f"开始上传图片到COS: {oss_path}")

            try:
                full_url = upload_to_oss(file_obj, oss_path)
                logger.info(f"上传成功，完整URL: {full_url}")
            except Exception as e:
                logger.error(f"上传到COS异常: {str(e)}", exc_info=True)
                return Response(
                    {'code': 500, 'message': '图片上传失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            if not full_url:
                logger.error("upload_to_oss 返回了空 URL")
                return Response(
                    {'code': 500, 'message': '图片上传失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            cos_domain = getattr(settings, 'COS_DOMAIN', '')
            if cos_domain and full_url.startswith(cos_domain):
                relative_path = full_url[len(cos_domain):].lstrip('/')
            else:
                relative_path = oss_path
            logger.info(f"相对路径: {relative_path}")

            old_url = guanshi.invite_qrcode_url
            if old_url:
                logger.info(f"删除旧二维码: {old_url}")
                try:
                    delete_from_oss(old_url)
                except Exception as e:
                    logger.warning(f"删除旧二维码失败(不影响主流程): {str(e)}")

            with transaction.atomic():
                guanshi.invite_qrcode_url = relative_path
                guanshi.save(update_fields=['invite_qrcode_url'])
                logger.info("数据库更新成功")

            return Response({
                'code': 0,
                'message': 'success',
                'data': {'url': relative_path}
            })

        except Exception as e:
            logger.error(f"生成二维码过程中发生未捕获异常: {str(e)}", exc_info=True)
            return Response(
                {'code': 500, 'message': '服务器内部错误', 'data': None},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

    def get_fresh_access_token(self):
        """
        直接请求微信接口获取新的 access_token，不缓存，保证最新。
        """
        appid = getattr(settings, 'WEIXIN_APPID', '')
        secret = getattr(settings, 'WEIXIN_SECRET', '')
        if not appid or not secret:
            logger.error("微信小程序配置缺失: WEIXIN_APPID 或 WEIXIN_SECRET 未设置")
            return None

        url = 'https://api.weixin.qq.com/cgi-bin/token'
        params = {
            'grant_type': 'client_credential',
            'appid': appid,
            'secret': secret
        }
        try:
            logger.info("正在请求微信接口获取最新 access_token...")
            resp = requests.get(url, params=params, timeout=5)
            data = resp.json()
            new_token = data.get('access_token')
            if new_token:
                logger.info("成功获取最新 access_token")
                return new_token
            else:
                errcode = data.get('errcode', '未知')
                errmsg = data.get('errmsg', '未知错误')
                logger.error(f"获取 access_token 失败，errcode={errcode}, errmsg={errmsg}")
                return None
        except Exception as e:
            logger.error(f"请求微信 access_token 接口异常: {str(e)}", exc_info=True)
            return None


class CYaoqingQRCodeView(APIView):
    """
    点单端老板推荐官小程序码（落地点单首页，按 UserUID 绑定，无邀请码）。
    POST /peizhi/cyaoqingewm
    """
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        current_user = request.user
        try:
            from jituan.services.club_context import resolve_effective_club_id
            from utils.weixin_token import get_club_mini_access_token, is_weixin_token_invalid
            from users.services.c_invite import get_or_create_c_yaoqing
            from users.models import YonghuCYaoqing

            if not getattr(current_user, 'UserUID', None):
                return Response(
                    {'code': 500, 'message': '用户身份异常', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            club_id = resolve_effective_club_id(request, current_user)
            profile = get_or_create_c_yaoqing(current_user)
            inviter_uid = str(current_user.UserUID)

            access_token = get_club_mini_access_token(club_id)
            if not access_token:
                return Response(
                    {'code': 500, 'message': '获取微信access_token失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            # 必须进点单首页，禁止进打手/管事页
            page_path = f'pages/index/index?yaoqingren={urllib.parse.quote(inviter_uid, safe="")}'
            post_data = {
                'path': page_path,
                'width': 430,
                'auto_color': False,
                'line_color': {'r': 0, 'g': 0, 'b': 0},
            }

            def _request_qr(token):
                try:
                    return requests.post(
                        f'https://api.weixin.qq.com/wxa/getwxacode?access_token={token}',
                        json=post_data,
                        timeout=10,
                    )
                except Exception as e:
                    logger.error('请求微信接口异常: %s', e, exc_info=True)
                    return None

            wx_resp = _request_qr(access_token)
            if wx_resp is None:
                return Response(
                    {'code': 500, 'message': '调用微信服务失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            if wx_resp.headers.get('content-type', '').startswith('application/json'):
                try:
                    err = wx_resp.json()
                    if is_weixin_token_invalid(err.get('errcode'), err.get('errmsg', '')):
                        access_token = get_club_mini_access_token(club_id, force_refresh=True)
                        if access_token:
                            wx_resp = _request_qr(access_token)
                except Exception:
                    pass

            if wx_resp.status_code != 200:
                errmsg = '未知错误'
                try:
                    errmsg = wx_resp.json().get('errmsg', errmsg)
                except Exception:
                    pass
                return Response(
                    {'code': 500, 'message': f'生成二维码失败: {errmsg}', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            content_type = wx_resp.headers.get('content-type', '')
            if 'image' not in content_type:
                try:
                    errmsg = wx_resp.json().get('errmsg', '未知错误')
                except Exception:
                    errmsg = '生成二维码失败'
                return Response(
                    {'code': 500, 'message': f'生成二维码失败: {errmsg}', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            timestamp = int(time.time())
            filename = f'{current_user.UserUID}_{timestamp}.png'
            oss_path = f'erweima/cuser/{club_id}/{filename}'
            file_obj = io.BytesIO(wx_resp.content)
            full_url = upload_to_oss(file_obj, oss_path)
            if not full_url:
                return Response(
                    {'code': 500, 'message': '图片上传失败', 'data': None},
                    status=status.HTTP_500_INTERNAL_SERVER_ERROR
                )

            cos_domain = getattr(settings, 'COS_DOMAIN', '')
            if cos_domain and full_url.startswith(cos_domain):
                relative_path = full_url[len(cos_domain):].lstrip('/')
            else:
                relative_path = oss_path

            old_url = profile.invite_qrcode_url
            if old_url:
                try:
                    delete_from_oss(old_url)
                except Exception as e:
                    logger.warning('删除旧点单端推荐二维码失败: %s', e)

            with transaction.atomic():
                locked = YonghuCYaoqing.objects.select_for_update().get(pk=profile.pk)
                locked.invite_qrcode_url = relative_path
                locked.save(update_fields=['invite_qrcode_url', 'UpdateTime'])

            return Response({
                'code': 0,
                'message': 'success',
                'data': {'url': relative_path, 'uid': inviter_uid},
            })
        except Exception as e:
            logger.error('生成点单端推荐二维码失败: %s', e, exc_info=True)
            return Response(
                {'code': 500, 'message': '服务器内部错误', 'data': None},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )


class ZuzhangHaibaoView(APIView):
    """
    组长专属海报二维码生成接口
    路径：/peizhi/zuzhanghb
    方法：POST
    权限：JWT认证，仅限组长身份
    返回：
        {
            "code": 0,
            "message": "success",
            "data": {
                "url": "erweima/zuzhang/1000001_1234567890.png",
                "yaoqingma": "ABC123XYZ"
            }
        }
    """
    permission_classes = [permissions.IsAuthenticated]

    def post(self, request):
        current_user = request.user
        from jituan.services.club_context import resolve_effective_club_id
        from utils.weixin_token import get_club_mini_access_token

        club_id = resolve_effective_club_id(request, current_user)

        # 1. 验证组长身份
        try:
            zuzhang = current_user.ZuzhangProfile
        except UserZuzhang.DoesNotExist:
            return Response({
                'code': 403,
                'message': '当前用户不是组长身份',
                'data': None
            }, status=status.HTTP_403_FORBIDDEN)

        # 2. 验证组长账号状态
        if zuzhang.zhuangtai != 1:
            return Response({
                'code': 403,
                'message': '组长账号状态异常，无法生成海报',
                'data': None
            }, status=status.HTTP_403_FORBIDDEN)

        # 3. 获取或生成邀请码
        invite_code = zuzhang.yaoqingma
        if not invite_code:
            invite_code = CreateInvitationCode(str(current_user.UserUID))
            if not VerifyInvitationCode(invite_code):
                logger.error(f"组长邀请码生成失败，用户: {current_user.UserUID}")
                return Response({
                    'code': 500,
                    'message': '邀请码生成失败',
                    'data': None
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            zuzhang.yaoqingma = invite_code
            zuzhang.save(update_fields=['yaoqingma'])

        # 4. 获取微信 access_token（按俱乐部对应小程序）
        access_token = get_club_mini_access_token(club_id, force_refresh=False)
        if not access_token:
            return Response({
                'code': 500,
                'message': '获取微信access_token失败',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # ===== 修改点：使用 A 接口（getwxacode） =====
        encoded_invite = urllib.parse.quote(invite_code, safe='')
        # 新小程序：打手端 Tab「我的」管事注册；旧码 guanshiduan 由小程序兼容页转发
        page_path = f'pages/fighter/fighter?registerType=guanshi&inviteCode={encoded_invite}'

        wx_resp, wx_error = self._request_wx_qrcode(page_path, access_token)
        if wx_error and is_weixin_token_invalid(wx_error.get('errcode'), wx_error.get('errmsg', '')):
            logger.warning('组长海报二维码 token 失效，强制刷新后重试 club=%s', club_id)
            access_token = get_club_mini_access_token(club_id, force_refresh=True)
            if not access_token:
                return Response({
                    'code': 500,
                    'message': '获取微信access_token失败',
                    'data': None
                }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
            wx_resp, wx_error = self._request_wx_qrcode(page_path, access_token)

        if wx_error:
            errmsg = wx_error.get('errmsg', '未知错误')
            logger.error(f"微信接口返回错误: {errmsg}")
            return Response({
                'code': 500,
                'message': f'生成二维码失败: {errmsg}',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        if wx_resp is None:
            return Response({
                'code': 500,
                'message': '调用微信服务失败',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # 5. 上传到腾讯云 COS
        timestamp = int(time.time())
        filename = f"{current_user.UserUID}_{timestamp}.png"
        oss_path = f"erweima/zuzhang/{club_id}/{filename}"
        file_obj = io.BytesIO(wx_resp.content)

        try:
            full_url = upload_to_oss(file_obj, oss_path)
        except Exception as e:
            logger.error(f"上传到COS失败: {e}")
            return Response({
                'code': 500,
                'message': '图片上传失败',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        if not full_url:
            return Response({
                'code': 500,
                'message': '图片上传失败',
                'data': None
            }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        # 6. 获取相对路径
        cos_domain = getattr(settings, 'COS_DOMAIN', '')
        if cos_domain and full_url.startswith(cos_domain):
            relative_path = full_url[len(cos_domain):].lstrip('/')
        else:
            relative_path = oss_path

        # 7. 删除旧二维码文件
        old_url = zuzhang.haibao_url
        if old_url:
            delete_from_oss(old_url)

        # 8. 更新数据库中的 haibao_url 字段
        with transaction.atomic():
            zuzhang.haibao_url = relative_path
            zuzhang.save(update_fields=['haibao_url'])

        # 9. 返回邀请码和二维码相对路径
        return Response({
            'code': 0,
            'message': 'success',
            'data': {
                'url': relative_path,
                'yaoqingma': invite_code
            }
        })

    def _request_wx_qrcode(self, page_path, access_token):
        """
        调用 getwxacode 生成小程序码。
        返回 (response, error_dict)；成功时 error_dict 为 None。
        """
        wx_url = f'https://api.weixin.qq.com/wxa/getwxacode?access_token={access_token}'
        post_data = {
            'path': page_path,
            'width': 430,
            'auto_color': False,
            'line_color': {'r': 0, 'g': 0, 'b': 0},
        }
        try:
            wx_resp = requests.post(wx_url, json=post_data, timeout=10)
        except Exception as e:
            logger.error(f'请求微信接口失败: {e}')
            return None, {'errmsg': '调用微信服务失败'}

        if wx_resp.status_code != 200:
            error_info = {}
            if wx_resp.headers.get('content-type', '').startswith('application/json'):
                try:
                    error_info = wx_resp.json()
                except Exception:
                    pass
            return wx_resp, {
                'errcode': error_info.get('errcode'),
                'errmsg': error_info.get('errmsg', f'HTTP {wx_resp.status_code}'),
            }

        content_type = wx_resp.headers.get('content-type', '')
        if 'image' not in content_type:
            try:
                error_info = wx_resp.json()
                return wx_resp, {
                    'errcode': error_info.get('errcode'),
                    'errmsg': error_info.get('errmsg', '未知错误'),
                }
            except Exception:
                return wx_resp, {'errmsg': '微信接口返回未知内容'}

        return wx_resp, None




