# dingdan/notice_tasks.py
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
from a_long_dianjing.celery import app
from users.models import OfficialAccountUser
from utils.weixin_broadcast import WeixinBroadcastSender

logger = logging.getLogger('weixin_broadcast')


@app.task(bind=True, max_retries=3, default_retry_delay=60)
def dingdan_guangbo(self, order_info):
    """
    异步并发广播订单模板消息给所有关注服务号的用户
    order_info 字典字段示例：
    {
        'OrderID': 'PT123456',
        'game_type': '王者荣耀',
        'amount': '88.00',
        'order_desc': '星耀上王者'
    }
    """
    sender = WeixinBroadcastSender()
    template_data = sender.build_order_message_data(order_info)
    order_id = order_info.get('OrderID')

    # 查询所有已关注用户
    try:
        openids = list(
            OfficialAccountUser.objects
            .filter(is_subscribed=True)
            .values_list('official_openid', flat=True)
        )
    except Exception as e:
        logger.error(f'查询订阅用户失败: {e}')
        raise self.retry(exc=e)

    if not openids:
        return {'success': True, 'msg': '无订阅用户'}

    total = len(openids)
    logger.info(f'📊 Celery 广播订单 {order_id} 开始，目标人数: {total}')

    success = 0
    fail = 0

    # 20 个线程并发发送，微信接口耗时 200-500ms，1000 人约 15 秒完成
    with ThreadPoolExecutor(max_workers=20) as executor:
        future_map = {
            executor.submit(sender.send_to_user, openid, template_data, order_id): openid
            for openid in openids
        }
        for future in as_completed(future_map):
            openid = future_map[future]
            try:
                res = future.result(timeout=30)
                if res.get('success'):
                    success += 1
                else:
                    fail += 1
            except Exception as e:
                fail += 1
                logger.error(f'发送异常 {openid[:10]}...: {e}')

    logger.info(f'🏁 广播完成：成功 {success}，失败 {fail}，总计 {total}')
    return {
        'success': True,
        'total': total,
        'success_count': success,
        'fail_count': fail
    }