"""
会员微信充值履约（回调 / 前端确认共用，幂等）。

分红策略（定稿 v2）：
- formal_cishu：本俱乐部 + 打手 + 会员，czjilu 已支付正式单(is_trial=false)笔数 + 1。
  与换管事无关；体验单不计入。
- formal_cishu=1：duoci_fenhong 或 club 默认正式分成。
- formal_cishu>=2：仅 duoci_fenhong；无配置则不分。
- 体验单：固定 trial_guanshifc / trial_zuzhangfc；不计入 formal_cishu。

财务：每笔成功履约必须 czjilu.zhuangtai=3 + 收支入账（record_wechat_income_once）。
"""
from __future__ import annotations

import hashlib
import logging
import random
import string
from datetime import timedelta
from decimal import Decimal

import defusedxml.ElementTree as ET
import requests
from django.db import transaction
from django.utils import timezone

from backend.utils import (
    update_guanshi_daily_by_action,
    update_guanshi_xufei_daily,
    update_zuzhang_daily_by_action,
)
from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_config import get_huiyuan_fenchong
from jituan.services.club_penalty import resolve_gsfenhong_club_id
from jituan.services.club_user import get_user_club_id
from jituan.services.wechat_pay import club_id_from_czjilu, get_wechat_v2_config
from products.models import Czjilu, DuociFenhong, Gsfenhong, Huiyuan, Huiyuangoumai
from users.business_models import User
from users.models import UserDashou, UserGuanshi, UserZuzhang

logger = logging.getLogger(__name__)

MEMBER_FENHONG_LEIXING = 1
MEMBER_LEIXING = 1
MEMBER_PAID_STATUS = 3
MEMBER_PENDING_STATUS = 9


class MemberRechargeError(Exception):
    def __init__(self, message, code='error'):
        super().__init__(message)
        self.code = code


def get_club_huiyuan_row(club_id, huiyuan_id, require_enabled=False):
    club_id = club_id or CLUB_ID_DEFAULT
    qs = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id)
    if require_enabled:
        qs = qs.filter(is_enabled=True)
    return qs.first()


def club_huiyuan_sellable(club_id, huiyuan_id, is_trial=False):
    """正式/体验是否可售及价格、天数。"""
    club_id = club_id or CLUB_ID_DEFAULT
    row = get_club_huiyuan_row(club_id, huiyuan_id, require_enabled=True)
    if not row:
        return False, Decimal('0'), 0

    if is_trial:
        if not row.trial_enabled:
            return False, Decimal('0'), 0
        price = Decimal(str(row.trial_price or 0))
        days = int(row.trial_days or 0)
        if price <= 0 or days <= 0:
            return False, Decimal('0'), 0
        return True, price, days

    price = Decimal(str(row.jiage or 0))
    days = int(row.formal_days or 30)
    if price <= 0:
        return False, Decimal('0'), 0
    return True, price, days


def has_formal_member_purchase(yonghuid, huiyuan_id, club_id):
    """是否已有成功支付的正式会员单（用于禁止再买体验）。"""
    club_id = club_id or CLUB_ID_DEFAULT
    return Czjilu.query.filter(
        yonghuid=yonghuid,
        huiyuan_id=huiyuan_id,
        leixing=MEMBER_LEIXING,
        zhuangtai=MEMBER_PAID_STATUS,
        club_id=club_id,
        is_trial=False,
    ).exists()


def can_purchase_trial(yonghuid, huiyuan_id, club_id):
    """体验会员是否允许购买。"""
    club_id = club_id or CLUB_ID_DEFAULT
    sellable, _, _ = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=True)
    if not sellable:
        return False, '本俱乐部未开启体验会员'

    goumai = Huiyuangoumai.query.filter(
        yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
    ).first()
    if goumai and goumai.has_used_trial:
        return False, '体验会员仅可购买一次'

    if has_formal_member_purchase(yonghuid, huiyuan_id, club_id):
        return False, '已购买过正式会员，不可再购买体验版'

    return True, None


def validate_member_purchase(yonghuid, huiyuan_id, club_id, is_trial):
    """下单前校验。"""
    is_trial = bool(is_trial)
    if is_trial:
        ok, msg = can_purchase_trial(yonghuid, huiyuan_id, club_id)
        if not ok:
            return False, msg, Decimal('0'), 0
        sellable, price, days = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=True)
    else:
        sellable, price, days = club_huiyuan_sellable(club_id, huiyuan_id, is_trial=False)
        if not sellable:
            return False, '该俱乐部未配置此会员或已下架', Decimal('0'), 0

    if not sellable:
        return False, '会员不可购买', Decimal('0'), 0
    return True, None, price, days


def resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
    """
    打手第几次正式购买该会员（duoci_fenhong.cishu）。
    仅统计 is_trial=false 的已支付 czjilu。
    """
    club_id = club_id or CLUB_ID_DEFAULT
    prior_paid = Czjilu.query.filter(
        yonghuid=yonghuid,
        huiyuan_id=huiyuan_id,
        leixing=MEMBER_LEIXING,
        zhuangtai=MEMBER_PAID_STATUS,
        club_id=club_id,
        is_trial=False,
    ).exclude(dingdan_id=current_dingdan_id).count()
    return prior_paid + 1


# 兼容旧名
def resolve_member_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id):
    return resolve_formal_purchase_cishu(yonghuid, huiyuan_id, current_dingdan_id, club_id)


def fulfill_member_balance_purchase(czjilu, huiyuan, club_id=None):
    """余额抵扣购买会员：按俱乐部配置天数履约，并计入购买次数/分红。"""
    club_id = club_id or getattr(czjilu, 'club_id', None) or CLUB_ID_DEFAULT
    is_trial = bool(getattr(czjilu, 'is_trial', False))
    purchase_days = int(getattr(czjilu, 'purchase_days', None) or 0)
    if purchase_days <= 0:
        _, _, purchase_days = club_huiyuan_sellable(club_id, czjilu.huiyuan_id, is_trial=is_trial)

    formal_cishu = None
    trial_guanshi_fc = Decimal('0')
    trial_zuzhangfc = Decimal('0')
    if is_trial:
        row = get_club_huiyuan_row(club_id, czjilu.huiyuan_id)
        if row:
            trial_guanshi_fc = Decimal(str(row.trial_guanshifc or 0))
            trial_zuzhangfc = Decimal(str(row.trial_zuzhangfc or 0))
    else:
        formal_cishu = resolve_formal_purchase_cishu(
            czjilu.yonghuid, czjilu.huiyuan_id, czjilu.dingdan_id, club_id,
        )

    goumai_record, is_first_buy_any = _apply_huiyuan_entitlement(
        czjilu.yonghuid, czjilu.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
    )
    if is_first_buy_any:
        _add_first_buy_jifen(czjilu.yonghuid)

    czjilu.zhuangtai = MEMBER_PAID_STATUS
    if not czjilu.purchase_days:
        czjilu.purchase_days = purchase_days
    czjilu.save(update_fields=['zhuangtai', 'purchase_days'])

    huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
    huiyuan.save(update_fields=['goumai_cishu'])

    try:
        _apply_member_fenhong(
            czjilu, huiyuan,
            formal_cishu=formal_cishu,
            trial_guanshi_fc=trial_guanshi_fc,
            trial_zuzhangfc=trial_zuzhangfc,
        )
    except Exception as exc:
        logger.error(
            '余额购会员分红失败 dingdan=%s: %s', czjilu.dingdan_id, exc, exc_info=True,
        )

    return goumai_record, is_first_buy_any


def _lookup_duoci_fenhong(club_id, huiyuan_id, beneficiary_id, cishu, field):
    """返回 (金额, 是否已配置行)。已配置为 0 时不应再回落到永久分红。"""
    try:
        row = DuociFenhong.query.get(
            club_id=club_id or CLUB_ID_DEFAULT,
            huiyuan=huiyuan_id,
            yonghuid=beneficiary_id,
            cishu=cishu,
        )
        return Decimal(str(getattr(row, field) or 0)), True
    except DuociFenhong.DoesNotExist:
        return None, False


def calc_guanshi_member_fenhong(huiyuan_id, guanshi_id, cishu, club_id, default_guanshi_fc):
    club_id = club_id or CLUB_ID_DEFAULT
    custom, configured = _lookup_duoci_fenhong(
        club_id, huiyuan_id, guanshi_id, cishu, 'guanshi_fenhong',
    )
    if configured:
        return custom if custom > 0 else None
    if cishu == 1:
        base = default_guanshi_fc if default_guanshi_fc is not None else Decimal('0')
        return base if base > 0 else None
    try:
        guanshi = UserGuanshi.query.get(user__UserUID=guanshi_id)
        if guanshi.fenghong_erci_enabled:
            permanent = Decimal(str(guanshi.fenghong_erci or 0))
            if permanent > 0:
                return permanent
    except UserGuanshi.DoesNotExist:
        pass
    return None


def calc_zuzhang_member_fenhong(huiyuan_id, zuzhang_id, cishu, club_id, default_zuzhang_fc):
    if not zuzhang_id:
        return None
    club_id = club_id or CLUB_ID_DEFAULT
    custom, configured = _lookup_duoci_fenhong(
        club_id, huiyuan_id, zuzhang_id, cishu, 'zuzhang_fenhong',
    )
    if configured:
        return custom if custom > 0 else None
    if cishu == 1:
        base = default_zuzhang_fc if default_zuzhang_fc is not None else Decimal('0')
        return base if base > 0 else None
    try:
        zuzhang = UserZuzhang.query.get(user__UserUID=zuzhang_id)
        if zuzhang.kaioi_ewai_fenhong:
            permanent = Decimal(str(zuzhang.ewai_fenhong_jine or 0))
            if permanent > 0:
                return permanent
    except UserZuzhang.DoesNotExist:
        pass
    return None


def _calc_new_daoqi(existing, days):
    """未过期叠加；已过期从今天算。"""
    now = timezone.now()
    days = max(1, int(days))
    if existing and existing.daoqi_time and existing.daoqi_time > now and existing.huiyuan_zhuangtai == 1:
        return existing.daoqi_time + timedelta(days=days)
    return now + timedelta(days=days)


def _record_member_income(order, club_id):
    """会员充值收入入账（幂等 biz_ref=cz:dingdan_id）。"""
    jine_decimal = Decimal(str(order.jine))
    try:
        from jituan.services.szjilu_accounting import record_wechat_income_once
        ok = record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{order.dingdan_id}')
        if not ok:
            from jituan.services.szjilu_accounting import repair_wechat_income_if_missing
            repair_wechat_income_if_missing(jine_decimal, club_id, biz_ref=f'cz:{order.dingdan_id}')
        logger.info(
            '会员收入入账 dingdan=%s club=%s jine=%s is_trial=%s',
            order.dingdan_id, club_id, jine_decimal, getattr(order, 'is_trial', False),
        )
    except Exception as exc:
        logger.error(
            '会员收入入账失败 dingdan=%s club=%s jine=%s err=%s',
            order.dingdan_id, club_id, order.jine, exc, exc_info=True,
        )
        raise MemberRechargeError('收入统计失败，请联系客服', 'income_error')


def _apply_huiyuan_entitlement(yonghuid, huiyuan_id, huiyuan, club_id, is_trial, purchase_days):
    """开通/续期会员权益。"""
    is_trial = bool(is_trial)
    purchase_days = max(1, int(purchase_days))
    existing = Huiyuangoumai.query.filter(
        yonghu_id=yonghuid, huiyuan_id=huiyuan_id,
    ).first()
    is_first_buy_any = False
    new_daoqi = _calc_new_daoqi(existing, purchase_days)

    if existing:
        existing.daoqi_time = new_daoqi
        existing.huiyuan_zhuangtai = 1
        existing.jieshao = huiyuan.jieshao
        if is_trial:
            existing.is_trial = True
            existing.has_used_trial = True
        else:
            existing.is_trial = False
        existing.save(update_fields=[
            'daoqi_time', 'huiyuan_zhuangtai', 'jieshao', 'is_trial', 'has_used_trial', 'UpdateTime',
        ])
        record = existing
    else:
        other = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(huiyuan_id=huiyuan_id).count()
        if other == 0:
            is_first_buy_any = True
        resolved_club = club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first())
        record = Huiyuangoumai.query.create(
            yonghu_id=yonghuid,
            huiyuan_id=huiyuan_id,
            jieshao=huiyuan.jieshao,
            huiyuan_zhuangtai=1,
            daoqi_time=new_daoqi,
            club_id=resolved_club,
            is_trial=is_trial,
            has_used_trial=is_trial,
        )
    return record, is_first_buy_any


def _apply_member_fenhong(order, huiyuan, formal_cishu=None, trial_guanshi_fc=None, trial_zuzhang_fc=None):
    if Gsfenhong.query.filter(dingdan_id=order.dingdan_id).exists():
        logger.info('会员分红已处理，跳过: %s', order.dingdan_id)
        return

    try:
        dashou = UserDashou.query.get(user__UserUID=order.yonghuid)
    except UserDashou.DoesNotExist:
        logger.warning('打手不存在，跳过会员分红: %s', order.yonghuid)
        return

    guanshi_id = dashou.yaoqingren
    if not guanshi_id:
        logger.info('打手%s无邀请管事，跳过会员分红', order.yonghuid)
        return

    try:
        guanshi = UserGuanshi.query.get(user__UserUID=guanshi_id)
    except UserGuanshi.DoesNotExist:
        logger.warning('管事不存在: %s', guanshi_id)
        return

    zuzhang_id = guanshi.yaoqingren
    zuzhang = None
    if zuzhang_id:
        try:
            zuzhang = UserZuzhang.query.get(user__UserUID=zuzhang_id)
        except UserZuzhang.DoesNotExist:
            logger.warning('组长不存在: %s', zuzhang_id)

    club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
    huiyuan_id = huiyuan.huiyuan_id
    order_amount = Decimal(str(order.jine))
    is_trial_order = bool(getattr(order, 'is_trial', False))

    if is_trial_order:
        guanshi_fenhong = Decimal(str(trial_guanshi_fc or 0))
        zuzhang_fenhong = Decimal(str(trial_zuzhang_fc or 0)) if zuzhang_id else Decimal('0')
        cishu = None
    else:
        cishu = formal_cishu or resolve_formal_purchase_cishu(
            order.yonghuid, huiyuan_id, order.dingdan_id, club_id,
        )
        guanshi_fc, zuzhang_fc = get_huiyuan_fenchong(club_id, huiyuan_id)
        guanshi_fenhong = calc_guanshi_member_fenhong(
            huiyuan_id, guanshi_id, cishu, club_id, guanshi_fc,
        )
        guanshi_fenhong = guanshi_fenhong if guanshi_fenhong is not None else Decimal('0')
        zuzhang_fenhong = calc_zuzhang_member_fenhong(
            huiyuan_id, zuzhang_id, cishu, club_id, zuzhang_fc,
        )
        zuzhang_fenhong = zuzhang_fenhong if zuzhang_fenhong is not None else Decimal('0')

    total = guanshi_fenhong + zuzhang_fenhong
    if total > order_amount:
        logger.warning('分红总额%s超订单%s，组长置0', total, order_amount)
        zuzhang_fenhong = Decimal('0')
        total = guanshi_fenhong
        if total > order_amount:
            logger.warning('管事分红%s超订单，置0', guanshi_fenhong)
            guanshi_fenhong = Decimal('0')

    if guanshi_fenhong <= 0 and zuzhang_fenhong <= 0:
        logger.info(
            '本次无会员分红 dingdan=%s club=%s cishu=%s is_trial=%s',
            order.dingdan_id, club_id, cishu, is_trial_order,
        )
        return

    user_main = dashou.user
    if guanshi_fenhong > 0:
        guanshi.chongzhifenrun += guanshi_fenhong
        guanshi.yue += guanshi_fenhong
        guanshi.jinrichongzhi += 1
        guanshi.jinyuechongzhi += 1
        guanshi.save(update_fields=[
            'chongzhifenrun', 'yue', 'jinrichongzhi', 'jinyuechongzhi',
        ])

    if zuzhang_fenhong > 0 and zuzhang:
        zuzhang.fenyong_zonge += zuzhang_fenhong
        zuzhang.ketixian_jine += zuzhang_fenhong
        zuzhang.jinri_fenyong += zuzhang_fenhong
        zuzhang.jinyue_fenyong += zuzhang_fenhong
        zuzhang.save(update_fields=[
            'fenyong_zonge', 'ketixian_jine', 'jinri_fenyong', 'jinyue_fenyong',
        ])

    Gsfenhong.query.create(
        dingdan_id=order.dingdan_id,
        guanshi=guanshi_id,
        dashouid=order.yonghuid,
        shuoming=order.shuoming,
        fenhong=guanshi_fenhong,
        avatar=user_main.Avatar,
        nicheng=dashou.nicheng or '未知打手',
        zuzhang_id=zuzhang_id if zuzhang_fenhong > 0 else None,
        zuzhang_fenhong=zuzhang_fenhong if zuzhang_fenhong > 0 else None,
        huiyuan_id=huiyuan_id,
        fenhong_leixing=MEMBER_FENHONG_LEIXING,
        club_id=resolve_gsfenhong_club_id(dingdan_id=order.dingdan_id, czjilu=order),
        is_trial=is_trial_order,
    )

    if guanshi_fenhong > 0 and not is_trial_order and cishu is not None:
        try:
            if cishu == 1:
                update_guanshi_daily_by_action(yonghuid=guanshi_id, action=2, amount=guanshi_fenhong)
            else:
                update_guanshi_xufei_daily(yonghuid=guanshi_id, xufei_jine=guanshi_fenhong)
        except Exception as exc:
            logger.error('管事日统计更新失败: %s', exc)

    if zuzhang_fenhong > 0 and zuzhang_id:
        try:
            update_zuzhang_daily_by_action(yonghuid=zuzhang_id, action=2, amount=zuzhang_fenhong)
        except Exception as exc:
            logger.error('组长日统计更新失败: %s', exc)

    logger.info(
        '会员分红完成 dingdan=%s club=%s formal_cishu=%s is_trial=%s 管事=%s 组长=%s',
        order.dingdan_id, club_id, cishu, is_trial_order, guanshi_fenhong, zuzhang_fenhong,
    )


def _add_first_buy_jifen(yonghuid):
    try:
        dashou = UserDashou.query.get(user__UserUID=yonghuid)
        dashou.jifen += 10
        dashou.save(update_fields=['jifen'])
    except UserDashou.DoesNotExist:
        logger.warning('首次购会员加积分失败，打手不存在: %s', yonghuid)


def repair_member_entitlement_if_paid(dingdan_id):
    order = Czjilu.query.filter(dingdan_id=dingdan_id, leixing=MEMBER_LEIXING).first()
    if not order or order.zhuangtai != MEMBER_PAID_STATUS or not order.huiyuan_id:
        return False

    exists = Huiyuangoumai.query.filter(
        yonghu_id=order.yonghuid, huiyuan_id=order.huiyuan_id,
    ).exists()
    if exists:
        return True

    try:
        huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id)
    except Huiyuan.DoesNotExist:
        logger.error('补开通会员失败 dingdan=%s huiyuan=%s', dingdan_id, order.huiyuan_id)
        return False

    club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
    days = order.purchase_days or 30
    with transaction.atomic():
        _apply_huiyuan_entitlement(
            order.yonghuid, order.huiyuan_id, huiyuan, club_id,
            bool(getattr(order, 'is_trial', False)), days,
        )
    logger.warning('已补开通会员 dingdan=%s', dingdan_id)
    return True


@transaction.atomic
def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'):
    locked = Czjilu.query.filter(dingdan_id=dingdan_id).select_for_update()
    order = locked.first()
    if not order:
        raise MemberRechargeError('订单不存在', 'not_found')

    if order.leixing != MEMBER_LEIXING:
        raise MemberRechargeError('订单类型不是会员充值', 'type_error')

    if order.zhuangtai != MEMBER_PENDING_STATUS:
        logger.info(
            '会员订单已处理，跳过履约 dingdan=%s zhuangtai=%s source=%s',
            dingdan_id, order.zhuangtai, source,
        )
        try:
            club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
            _record_member_income(order, club_id)
        except MemberRechargeError:
            pass
        return {
            'already_done': True,
            'dingdan_id': dingdan_id,
            'zhuangtai': order.zhuangtai,
        }

    if paid_amount_yuan is not None:
        paid = Decimal(str(paid_amount_yuan))
        order_amt = Decimal(str(order.jine))
        if abs(paid - order_amt) > Decimal('0.01'):
            raise MemberRechargeError(
                f'支付金额{paid}与订单{order_amt}不一致', 'amount_mismatch',
            )

    huiyuan = Huiyuan.query.filter(huiyuan_id=order.huiyuan_id).first()
    if not huiyuan:
        raise MemberRechargeError(f'会员不存在: {order.huiyuan_id}', 'huiyuan_not_found')

    club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT
    is_trial = bool(getattr(order, 'is_trial', False))
    purchase_days = order.purchase_days or (30 if not is_trial else 0)
    if purchase_days <= 0:
        _, _, purchase_days = club_huiyuan_sellable(club_id, order.huiyuan_id, is_trial=is_trial)

    formal_cishu = None
    trial_guanshi_fc = Decimal('0')
    trial_zuzhang_fc = Decimal('0')
    if is_trial:
        row = get_club_huiyuan_row(club_id, order.huiyuan_id)
        if row:
            trial_guanshi_fc = Decimal(str(row.trial_guanshifc or 0))
            trial_zuzhang_fc = Decimal(str(row.trial_zuzhangfc or 0))
    else:
        formal_cishu = resolve_formal_purchase_cishu(
            order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id,
        )

    goumai_record, is_first_buy_any = _apply_huiyuan_entitlement(
        order.yonghuid, order.huiyuan_id, huiyuan, club_id, is_trial, purchase_days,
    )
    if is_first_buy_any:
        _add_first_buy_jifen(order.yonghuid)

    order.zhuangtai = MEMBER_PAID_STATUS
    order.save(update_fields=['zhuangtai'])

    huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1
    huiyuan.save(update_fields=['goumai_cishu'])

    _record_member_income(order, club_id)

    try:
        _apply_member_fenhong(
            order, huiyuan,
            formal_cishu=formal_cishu,
            trial_guanshi_fc=trial_guanshi_fc,
            trial_zuzhang_fc=trial_zuzhang_fc,
        )
    except Exception as exc:
        logger.error(
            '会员分红失败但会员已开通 dingdan=%s err=%s', dingdan_id, exc, exc_info=True,
        )

    logger.info(
        '会员充值履约完成 dingdan=%s source=%s club=%s is_trial=%s formal_cishu=%s jine=%s',
        dingdan_id, source, club_id, is_trial, formal_cishu, order.jine,
    )
    return {
        'already_done': False,
        'dingdan_id': dingdan_id,
        'zhuangtai': MEMBER_PAID_STATUS,
        'formal_cishu': formal_cishu,
        'is_trial': is_trial,
        'daoqi_time': goumai_record.daoqi_time,
        'huiyuan_id': order.huiyuan_id,
    }


def query_wechat_v2_trade_state(out_trade_no, club_id=None):
    club_id = club_id or club_id_from_czjilu(out_trade_no)
    cfg = get_wechat_v2_config(club_id)
    nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
    params = {
        'appid': cfg['appid'],
        'mch_id': cfg['mch_id'],
        'out_trade_no': out_trade_no,
        'nonce_str': nonce_str,
    }
    string_a = '&'.join(f'{k}={params[k]}' for k in sorted(params.keys()))
    sign = hashlib.md5(f'{string_a}&key={cfg["key"]}'.encode()).hexdigest().upper()
    params['sign'] = sign
    xml_body = ''.join(f'<{k}>{params[k]}</{k}>' for k in params)
    xml_data = f'<xml>{xml_body}</xml>'

    try:
        resp = requests.post(
            'https://api.mch.weixin.qq.com/pay/orderquery',
            data=xml_data.encode('utf-8'),
            headers={'Content-Type': 'application/xml'},
            timeout=10,
        )
        root = ET.fromstring(resp.content)
        if root.find('return_code').text != 'SUCCESS':
            return None, None
        if root.find('result_code').text != 'SUCCESS':
            return None, None
        trade_state = root.find('trade_state').text
        txn = root.find('transaction_id')
        transaction_id = txn.text if txn is not None else None
        return trade_state, transaction_id
    except Exception as exc:
        logger.error('微信查单失败 %s: %s', out_trade_no, exc)
        return None, None


def confirm_member_recharge_paid(dingdan_id, yonghuid):
    try:
        order = Czjilu.query.get(dingdan_id=dingdan_id, yonghuid=yonghuid)
    except Czjilu.DoesNotExist:
        raise MemberRechargeError('订单不存在或不属于当前用户', 'not_found')

    if order.leixing != MEMBER_LEIXING:
        raise MemberRechargeError('不是会员订单', 'type_error')

    if order.zhuangtai == MEMBER_PAID_STATUS:
        goumai = Huiyuangoumai.query.filter(
            yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id,
        ).first()
        if not goumai:
            repair_member_entitlement_if_paid(dingdan_id)
            goumai = Huiyuangoumai.query.filter(
                yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id,
            ).first()
        if not goumai:
            raise MemberRechargeError('订单已付但会员记录缺失，请联系客服', 'data_error')
        return {
            'fulfilled': False,
            'already_done': True,
            'zhuangtai': MEMBER_PAID_STATUS,
            'daoqi_time': goumai.daoqi_time,
            'huiyuan_id': order.huiyuan_id,
            'is_trial': goumai.is_trial,
        }

    if order.zhuangtai != MEMBER_PENDING_STATUS:
        raise MemberRechargeError(f'订单状态异常: {order.zhuangtai}', 'status_error')

    trade_state, _ = query_wechat_v2_trade_state(
        dingdan_id, getattr(order, 'club_id', None),
    )
    if trade_state != 'SUCCESS':
        raise MemberRechargeError('微信侧尚未支付成功，请稍后再试', 'not_paid')

    total_fee_yuan = Decimal(str(order.jine))
    result = fulfill_member_recharge(
        dingdan_id, paid_amount_yuan=total_fee_yuan, source='confirm',
    )
    result['fulfilled'] = not result.get('already_done')
    return result


def dashou_in_active_trial_period(yonghuid):
    """打手是否处于未过期的体验会员期（限制佣金提现）。"""
    now = timezone.now()
    return Huiyuangoumai.query.filter(
        yonghu_id=yonghuid,
        is_trial=True,
        huiyuan_zhuangtai=1,
        daoqi_time__gt=now,
    ).exists()


def extend_member_entitlement_admin(yonghuid, huiyuan_id, days, club_id=None):
    """后台赠送正式会员（无 czjilu、无分红）。"""
    try:
        huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
    except Huiyuan.DoesNotExist:
        return None, '会员类型不存在'
    days = max(1, int(days))
    record, _ = _apply_huiyuan_entitlement(
        yonghuid, huiyuan_id, huiyuan, club_id, is_trial=False, purchase_days=days,
    )
    return record, None
