"""组队招募核心服务（新域）。无有效组队时所有挂钩函数立即返回，不影响旧逻辑。"""
from __future__ import annotations

import logging
from decimal import Decimal, ROUND_DOWN
from typing import Optional

from django.db import transaction
from django.db.models import F
from django.utils import timezone

logger = logging.getLogger(__name__)

MONEY = Decimal('0.01')


def _d(v) -> Decimal:
    try:
        return Decimal(str(v or 0)).quantize(MONEY)
    except Exception:
        return Decimal('0.00')


def _log(club_id, order_id, recruit_id, actor, action, detail=''):
    try:
        from orders.models import TeamActionLog
        TeamActionLog(
            club_id=(club_id or '')[:16],
            order_id=(order_id or '')[:32],
            recruit_id=recruit_id,
            actor_uid=(actor or '')[:32],
            action=(action or '')[:32],
            detail=(detail or '')[:2000],
        ).save()
    except Exception as e:
        logger.warning('TeamActionLog 失败: %s', e)


def calc_display_share(commission: Decimal, mode: str, need_count: int, per_person: Decimal) -> Decimal:
    c = _d(commission)
    need = max(1, min(3, int(need_count or 1)))
    if mode == 'fixed':
        return _d(per_person)
    # average: C / (1+need)
    n = 1 + need
    if n <= 0 or c <= 0:
        return Decimal('0.00')
    return (c / Decimal(n)).quantize(MONEY, rounding=ROUND_DOWN)


def calc_lock_shares(commission: Decimal, mode: str, per_person: Decimal, leader_uid: str, member_uids: list) -> dict:
    """返回 {uid: amount}，合计等于 C（尾差给队长）。member_uids 不含队长。"""
    c = _d(commission)
    members = [u for u in (member_uids or []) if u and u != leader_uid]
    n = 1 + len(members)
    out = {}
    if n <= 0 or c <= 0:
        return {leader_uid: c} if leader_uid else {}

    if mode == 'fixed':
        p = _d(per_person)
        for uid in members:
            out[uid] = p
        out[leader_uid] = _d(c - p * len(members))
        if out[leader_uid] < 0:
            out[leader_uid] = Decimal('0.00')
        return out

    base = (c / Decimal(n)).quantize(MONEY, rounding=ROUND_DOWN)
    for uid in members:
        out[uid] = base
    out[leader_uid] = _d(c - base * len(members))
    return out


def get_active_recruit(order_id: str):
    from orders.models import TeamRecruit
    return TeamRecruit.query.filter(
        order_id=order_id,
        status__in=[TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED],
    ).first()


def get_team_role_for_user(order_id: str, user_uid: str) -> dict:
    """
    当前用户在本单组队中的角色。
    返回: has_team, is_leader, is_member, leader_uid, my_share, recruit
    """
    from orders.models import TeamMember, TeamRecruit
    out = {
        'has_team': False,
        'is_leader': False,
        'is_member': False,
        'leader_uid': '',
        'my_share': None,
        'recruit': None,
    }
    oid = (order_id or '').strip()
    uid = (user_uid or '').strip()
    if not oid or not uid:
        return out
    rec = get_active_recruit(oid)
    if not rec:
        rec = TeamRecruit.query.filter(order_id=oid).order_by('-id').first()
    if not rec:
        return out
    out['has_team'] = True
    out['recruit'] = rec
    out['leader_uid'] = rec.leader_uid or ''
    out['is_leader'] = (rec.leader_uid or '') == uid
    mem = TeamMember.query.filter(
        recruit_id=rec.id, user_uid=uid, join_status=TeamMember.JOIN_ACTIVE,
    ).first()
    if mem:
        out['is_member'] = True
        out['my_share'] = float(_d(mem.share_amount))
    elif out['is_leader']:
        out['is_member'] = True
    return out


def ensure_team_leader_can_submit(order, user_uid: str) -> Optional[str]:
    """
    有有效组队时：仅队长可提交/修改交付。
    无组队时：须为订单 PlayerID。
    返回错误文案；通过返回 None。
    """
    oid = getattr(order, 'OrderID', None) or ''
    uid = (user_uid or '').strip()
    player = (getattr(order, 'PlayerID', None) or '').strip()
    from orders.models import TeamRecruit
    ctx = get_team_role_for_user(oid, uid)
    if ctx['has_team'] and ctx['recruit'] and int(ctx['recruit'].status or 0) in (
        TeamRecruit.STATUS_RECRUITING, TeamRecruit.STATUS_LOCKED,
    ):
        if not ctx['is_leader']:
            return '仅队长可提交或修改交付内容'
        return None
    if player and player != uid:
        return '订单不存在或没有提交权限'
    return None


def list_team_split_penalties(order_id: str, *, include_rejected=False):
    """同订单【组队均摊】罚单列表（按创建顺序）。"""
    from orders.models import Penalty
    qs = Penalty.query.filter(
        RelatedOrderID=order_id,
        Reason__startswith='【组队均摊】',
    )
    if not include_rejected:
        qs = qs.exclude(Status=4)
    return list(qs.order_by('id'))


def team_should_split(order) -> bool:
    """结单是否走组队分账：已锁定（或招募中但已有人——进8会强制锁）且有在队成员。"""
    from orders.models import TeamRecruit, TeamMember
    oid = getattr(order, 'OrderID', None) or ''
    if not oid:
        return False
    rec = TeamRecruit.query.filter(
        order_id=oid,
        status=TeamRecruit.STATUS_LOCKED,
    ).first()
    if not rec:
        return False
    return TeamMember.query.filter(
        recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE,
    ).exists()


def _set_dashou_busy(uid: str, busy: bool):
    from users.models import UserDashou
    from users.business_models import User
    u = User.query.filter(UserUID=uid).first()
    if not u:
        return
    UserDashou.objects.filter(user=u).update(zhuangtai=0 if busy else 1)


def _dashou_display_name(uid: str) -> str:
    try:
        from users.business_models import User
        from users.models import UserDashou
        u = User.query.filter(UserUID=uid).first()
        if not u:
            return f'打手{(uid or "")[:6]}'
        try:
            p = u.DashouProfile
            if p and p.nicheng:
                return p.nicheng
        except Exception:
            pass
        try:
            p = UserDashou.query.filter(user=u).first()
            if p and p.nicheng:
                return p.nicheng
        except Exception:
            pass
        return f'打手{(uid or "")[:6]}'
    except Exception:
        return f'打手{(uid or "")[:6]}'


def _sync_team_order_group_chat(order, dashou_uids: list, *, announce_uid=None, announce_text=None):
    """
    组队复用抢单配对群（不另建 group_{订单}）：
    - 订阅：打手们 + 商家/老板到原群
    - 可选：在原群发开组/入队公告
    """
    oid = (getattr(order, 'OrderID', None) or '').strip()
    if not oid:
        return False
    try:
        from utils.chat_utils import (
            _subscribe_users_to_group,
            _send_group_message,
            _get_self_goeasy_appkey,
            _get_self_goeasy_secret,
            _get_local_partner_info,
            _full_local_avatar,
            resolve_order_chat_group_id,
        )
        appkey = _get_self_goeasy_appkey()
        secret = _get_self_goeasy_secret()
        if not appkey:
            return False

        group_id = resolve_order_chat_group_id(order) or f'group_{oid}'
        user_ids = []
        for u in dashou_uids or []:
            u = (u or '').strip()
            if u:
                gid = f'Ds{u}'
                if gid not in user_ids:
                    user_ids.append(gid)

        partner_id, partner_name, partner_avatar = _get_local_partner_info(order)
        if partner_id and partner_id not in user_ids:
            user_ids.append(partner_id)

        if not user_ids:
            return False

        ok = _subscribe_users_to_group(user_ids, [group_id], appkey, secret)
        if not ok:
            logger.warning('组队订阅群失败 order=%s group=%s users=%s', oid, group_id, user_ids)

        if announce_uid and announce_text:
            sender_id = f'Ds{announce_uid}'
            sender_name = _dashou_display_name(announce_uid)
            sender_avatar = ''
            try:
                from users.business_models import User
                uu = User.query.filter(UserUID=announce_uid).first()
                if uu:
                    sender_avatar = _full_local_avatar(getattr(uu, 'Avatar', None) or '')
            except Exception:
                pass
            desc = (order.Description or '')[:20]
            group_name = (desc + '…') if order.Description and len(order.Description) > 20 else (order.Description or f'订单{oid}')
            sent = _send_group_message(
                appkey, secret, group_id, sender_id, sender_name, sender_avatar,
                announce_text, None, group_name=group_name, order_id=oid,
            )
            if not sent:
                logger.warning('组队群消息发送失败 order=%s group=%s text=%s', oid, group_id, announce_text)
            return bool(ok and sent)
        return bool(ok)
    except Exception as e:
        logger.warning('组队群同步失败 order=%s: %s', oid, e)
        return False


@transaction.atomic
def open_recruit(
    *,
    order,
    leader_uid: str,
    need_count: int,
    share_mode: str,
    per_person_amount=None,
    remark: str = '',
    leader_game_id: str = '',
    min_deal_rate=None,
    max_fine_rate=None,
):
    from orders.models import TeamRecruit, TeamMember

    oid = order.OrderID
    club_id = (getattr(order, 'ClubID', None) or '').strip() or 'xq'
    if int(order.Status or 0) != 2:
        raise ValueError('仅进行中订单可开启组队')
    if (order.PlayerID or '') != leader_uid:
        raise ValueError('仅接单打手可开启组队')
    if TeamRecruit.query.filter(order_id=oid).exclude(
        status__in=[TeamRecruit.STATUS_CANCELLED, TeamRecruit.STATUS_VOID, TeamRecruit.STATUS_FINISHED]
    ).exists():
        raise ValueError('本单已有进行中的组队')

    need = int(need_count or 0)
    if need < 1 or need > 3:
        raise ValueError('招募人数须为1～3')
    mode = (share_mode or '').strip().lower()
    if mode not in (TeamRecruit.MODE_FIXED, TeamRecruit.MODE_AVERAGE):
        raise ValueError('请选择固定值或平均值')

    c = _d(order.PlayerCommission)
    if c <= 0:
        raise ValueError('打手分成无效')

    p = _d(per_person_amount)
    if mode == TeamRecruit.MODE_FIXED:
        if p <= 0:
            raise ValueError('请设置固定人均金额')
        if p * (1 + need) > c:
            raise ValueError('人均×(队长+目标人数)不能超过打手分成')
    game_id = (leader_game_id or '').strip()
    if not game_id:
        raise ValueError('请填写队长游戏ID')

    display = calc_display_share(c, mode, need, p)
    now = timezone.now()
    rec = TeamRecruit(
        club_id=club_id,
        order_id=oid,
        leader_uid=leader_uid,
        status=TeamRecruit.STATUS_RECRUITING,
        need_count=need,
        joined_count=0,
        share_mode=mode,
        per_person_amount=p if mode == TeamRecruit.MODE_FIXED else Decimal('0.00'),
        display_share_amount=display,
        commission_snap=c,
        order_amount_snap=_d(order.Amount),
        remark=(remark or '')[:200],
        leader_game_id=game_id[:64],
        min_deal_rate=_d(min_deal_rate) if min_deal_rate not in (None, '') else None,
        max_fine_rate=_d(max_fine_rate) if max_fine_rate not in (None, '') else None,
        opened_at=now,
    )
    # Decimal null fields
    if min_deal_rate in (None, ''):
        rec.min_deal_rate = None
    if max_fine_rate in (None, ''):
        rec.max_fine_rate = None
    rec.save()

    TeamMember(
        club_id=club_id,
        recruit_id=rec.id,
        order_id=oid,
        user_uid=leader_uid,
        role=TeamMember.ROLE_LEADER,
        game_id=game_id[:64],
        share_amount=Decimal('0.00'),
        join_status=TeamMember.JOIN_ACTIVE,
        joined_at=now,
    ).save()

    name = _dashou_display_name(leader_uid)
    _sync_team_order_group_chat(
        order,
        [leader_uid],
        announce_uid=leader_uid,
        announce_text=f'{name} 已开启组队招募，可在本群沟通',
    )
    _log(club_id, oid, rec.id, leader_uid, 'open', f'mode={mode} need={need} display={display}')
    return rec


@transaction.atomic
def update_recruit(*, recruit_id: int, leader_uid: str, need_count=None, remark=None,
                   min_deal_rate='__omit__', max_fine_rate='__omit__'):
    from orders.models import TeamRecruit, Order

    rec = TeamRecruit.objects.select_for_update().filter(id=recruit_id).first()
    if not rec:
        raise ValueError('组队不存在')
    if rec.leader_uid != leader_uid:
        raise ValueError('仅队长可修改')
    if rec.status != TeamRecruit.STATUS_RECRUITING:
        raise ValueError('仅招募中可修改')

    order = Order.query.filter(OrderID=rec.order_id).first()
    if not order or int(order.Status or 0) != 2:
        raise ValueError('订单非进行中，无法修改招募')

    if need_count is not None:
        need = int(need_count)
        if need < 1 or need > 3:
            raise ValueError('招募人数须为1～3')
        if need < int(rec.joined_count or 0):
            raise ValueError('目标人数不能小于已入队人数')
        if rec.share_mode == TeamRecruit.MODE_FIXED:
            p = _d(rec.per_person_amount)
            c = _d(order.PlayerCommission) or _d(rec.commission_snap)
            if p * (1 + need) > c:
                raise ValueError('人均×(队长+目标人数)不能超过打手分成')
        rec.need_count = need

    if remark is not None:
        rec.remark = (remark or '')[:200]
    if min_deal_rate != '__omit__':
        rec.min_deal_rate = _d(min_deal_rate) if min_deal_rate not in (None, '') else None
    if max_fine_rate != '__omit__':
        rec.max_fine_rate = _d(max_fine_rate) if max_fine_rate not in (None, '') else None

    c = _d(order.PlayerCommission) or _d(rec.commission_snap)
    rec.display_share_amount = calc_display_share(
        c, rec.share_mode, rec.need_count, rec.per_person_amount,
    )
    rec.save()
    _log(rec.club_id, rec.order_id, rec.id, leader_uid, 'update', f'need={rec.need_count}')
    return rec


@transaction.atomic
def lock_recruit(rec_or_id, *, actor_uid='', reason='manual'):
    from orders.models import TeamRecruit, TeamMember, Order

    rid = rec_or_id if isinstance(rec_or_id, int) else rec_or_id.id
    rec = TeamRecruit.objects.select_for_update().filter(id=rid).first()
    if not rec:
        return None
    if rec.status == TeamRecruit.STATUS_LOCKED:
        return rec
    if rec.status != TeamRecruit.STATUS_RECRUITING:
        raise ValueError('当前状态不可锁定')

    order = Order.query.filter(OrderID=rec.order_id).first()
    c = _d(getattr(order, 'PlayerCommission', None) if order else None) or _d(rec.commission_snap)
    members = list(
        TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE)
        .exclude(role=TeamMember.ROLE_LEADER)
        .values_list('user_uid', flat=True)
    )
    shares = calc_lock_shares(c, rec.share_mode, rec.per_person_amount, rec.leader_uid, list(members))
    now = timezone.now()
    for uid, amt in shares.items():
        TeamMember.objects.filter(
            recruit_id=rec.id, user_uid=uid, join_status=TeamMember.JOIN_ACTIVE,
        ).update(share_amount=amt, UpdateTime=now)

    rec.status = TeamRecruit.STATUS_LOCKED
    rec.locked_at = now
    rec.display_share_amount = shares.get(
        next(iter(members), rec.leader_uid),
        calc_display_share(c, rec.share_mode, rec.need_count, rec.per_person_amount),
    )
    # 队友展示用：取任一队友份额，无队友则用队长外均分预览
    if members:
        rec.display_share_amount = shares.get(members[0], Decimal('0.00'))
    rec.save()
    _log(rec.club_id, rec.order_id, rec.id, actor_uid, 'lock', reason)
    return rec


def on_order_enter_status_8(order):
    """进结算中：招募中强制锁定。不改 eligible。"""
    try:
        from orders.models import TeamRecruit
        oid = getattr(order, 'OrderID', '') or ''
        rec = TeamRecruit.query.filter(order_id=oid, status=TeamRecruit.STATUS_RECRUITING).first()
        if rec:
            lock_recruit(rec.id, actor_uid='system', reason='enter_status_8')
    except Exception as e:
        logger.error('组队进8锁定失败: %s', e)


@transaction.atomic
def join_recruit(*, recruit_id: int, user_uid: str, game_id: str, request=None, require_club_id=None):
    from orders.models import TeamRecruit, TeamMember, Order
    from users.models import UserDashou
    from utils.fadan_utils import check_fadan_qiangdan_eligible

    game_id = (game_id or '').strip()
    if not game_id:
        raise ValueError('请填写游戏ID')

    rec = TeamRecruit.objects.select_for_update().filter(id=recruit_id).first()
    if not rec:
        raise ValueError('组队不存在')
    # 组队强制本俱乐部：不认跨店互通抢单
    if require_club_id and (rec.club_id or '') != require_club_id:
        raise ValueError('仅本俱乐部可加入组队')
    if rec.status != TeamRecruit.STATUS_RECRUITING:
        raise ValueError('招募已结束')
    if int(rec.joined_count or 0) >= int(rec.need_count or 0):
        raise ValueError('人数已满')
    if rec.leader_uid == user_uid:
        raise ValueError('队长无需加入')
    if TeamMember.query.filter(recruit_id=rec.id, user_uid=user_uid, join_status=TeamMember.JOIN_ACTIVE).exists():
        raise ValueError('您已在队中')

    order = Order.query.filter(OrderID=rec.order_id).first()
    if not order or int(order.Status or 0) != 2:
        raise ValueError('订单状态不可加入')
    if (getattr(order, 'ClubID', '') or '') != rec.club_id:
        raise ValueError('俱乐部不匹配')
    if require_club_id and (order.ClubID or '') != require_club_id:
        raise ValueError('仅本俱乐部订单可加入组队')

    # 忙碌 / 罚单
    try:
        from users.business_models import User
        user_obj = User.query.filter(UserUID=user_uid).first()
        if not user_obj:
            raise ValueError('用户不存在')
        profile = UserDashou.query.filter(user=user_obj).first()
        if not profile:
            raise ValueError('非打手身份')
        if int(getattr(profile, 'zhuangtai', 0) or 0) != 1:
            raise ValueError('您有订单正在接待中')
        if int(getattr(profile, 'zhanghaozhuangtai', 1) or 1) != 1:
            raise ValueError('账号状态异常')
    except ValueError:
        raise
    except Exception:
        raise ValueError('打手信息异常')

    ok, msg = check_fadan_qiangdan_eligible(user_uid, 1)
    if not ok:
        raise ValueError(msg or '存在未处理罚单')

    # 不能同时在其它进行中组队
    busy_other = TeamMember.query.filter(
        user_uid=user_uid,
        join_status=TeamMember.JOIN_ACTIVE,
        role=TeamMember.ROLE_MEMBER,
    ).exclude(order_id=rec.order_id)
    if busy_other.exists():
        # 仅当对应帖仍 recruiting/locked
        from orders.models import TeamRecruit as TR
        for m in busy_other[:5]:
            r = TR.query.filter(id=m.recruit_id, status__in=[TR.STATUS_RECRUITING, TR.STATUS_LOCKED]).first()
            if r:
                raise ValueError('您已在其他组队中')

    # 可选指标门槛
    if rec.min_deal_rate is not None or rec.max_fine_rate is not None:
        from jituan.models import DashouDealStat
        from jituan.services.deal_stats import serialize_dashou_stat
        row = DashouDealStat.query.filter(club_id=rec.club_id, dashou_uid=user_uid).first()
        deal_rate = 0.0
        fine_rate = 0.0
        if row and int(row.order_count or 0) > 0:
            s = serialize_dashou_stat(row)
            deal_rate = float(s.get('deal_rate') or 0)
            fine_rate = float(s.get('fined_rate') or 0)
        if rec.min_deal_rate is not None and deal_rate < float(rec.min_deal_rate):
            raise ValueError('成交率未达到队长要求')
        if rec.max_fine_rate is not None and fine_rate > float(rec.max_fine_rate):
            raise ValueError('罚款率超过队长要求')

    # 原单会员/押金
    if order.GrabRequirement == 1:
        from jituan.services.huiyuan_bundle import user_has_huiyuan_access_any, resolve_order_membership_ids
        required_ids = resolve_order_membership_ids({
            'MembershipID': order.MembershipID,
            'GrabRequirement': order.GrabRequirement,
            'ProductTypeID': order.ProductTypeID,
        })
        if not user_has_huiyuan_access_any(user_uid, required_ids, club_id=rec.club_id):
            raise ValueError('未开通对应会员或会员已过期')
    elif order.GrabRequirement == 2:
        yajin_required = order.CommissionReq or 0
        if (profile.yajin or 0) < yajin_required:
            raise ValueError(f'押金不足，需要{yajin_required}元')

    now = timezone.now()
    TeamMember(
        club_id=rec.club_id,
        recruit_id=rec.id,
        order_id=rec.order_id,
        user_uid=user_uid,
        role=TeamMember.ROLE_MEMBER,
        game_id=game_id[:64],
        share_amount=_d(rec.display_share_amount) if rec.share_mode == TeamRecruit.MODE_FIXED else Decimal('0.00'),
        join_status=TeamMember.JOIN_ACTIVE,
        joined_at=now,
    ).save()
    TeamRecruit.objects.filter(id=rec.id).update(joined_count=F('joined_count') + 1, UpdateTime=now)
    rec.refresh_from_db()
    _set_dashou_busy(user_uid, True)
    name = _dashou_display_name(user_uid)
    _sync_team_order_group_chat(
        order,
        [user_uid, rec.leader_uid, order.PlayerID],
        announce_uid=user_uid,
        announce_text=f'{name} 已组队',
    )
    _log(rec.club_id, rec.order_id, rec.id, user_uid, 'join', game_id)

    if int(rec.joined_count or 0) >= int(rec.need_count or 0):
        lock_recruit(rec.id, actor_uid=user_uid, reason='full')
        rec.refresh_from_db()
    return rec


@transaction.atomic
def clear_recruit(*, order_id: str, status: int, actor_uid='system', reason=''):
    """取消/作废：清退成员、置空闲、零分账。status=4取消或5作废。"""
    from orders.models import TeamRecruit, TeamMember

    rec = TeamRecruit.objects.select_for_update().filter(order_id=order_id).exclude(
        status__in=[TeamRecruit.STATUS_FINISHED, TeamRecruit.STATUS_CANCELLED, TeamRecruit.STATUS_VOID],
    ).first()
    if not rec:
        return None
    members = list(
        TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE).values_list('user_uid', 'role')
    )
    now = timezone.now()
    TeamMember.objects.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE).update(
        join_status=TeamMember.JOIN_CLEARED, UpdateTime=now,
    )
    for uid, role in members:
        if role == TeamMember.ROLE_MEMBER:
            _set_dashou_busy(uid, False)
        # 队长忙碌由订单主流程管理，此处不强制改队长
    rec.status = status
    rec.closed_at = now
    rec.save(update_fields=['status', 'closed_at', 'UpdateTime'])
    _log(rec.club_id, order_id, rec.id, actor_uid, 'clear', f'{reason} ->{status}')
    return rec


def on_order_refunded(order):
    try:
        from orders.models import TeamRecruit
        clear_recruit(order_id=order.OrderID, status=TeamRecruit.STATUS_CANCELLED, reason='refund')
    except Exception as e:
        logger.error('组队退款清退失败: %s', e)


def on_order_change_player(order_id: str, actor_uid='system'):
    try:
        from orders.models import TeamRecruit
        clear_recruit(order_id=order_id, status=TeamRecruit.STATUS_VOID, actor_uid=actor_uid, reason='change_player')
    except Exception as e:
        logger.error('组队换打手作废失败: %s', e)


@transaction.atomic
def settle_team_or_legacy(order, *, set_idle_leader=False) -> bool:
    """
    若应组队分账则多人入账并返回 True；否则返回 False 由调用方走原逻辑。
    调用方须已在 atomic 且 ideally select_for_update 订单。
    """
    from orders.models import TeamRecruit, TeamMember, TeamSettleLedger, Order
    from users.business_models import User
    from jituan.services.fund_freeze import credit_dashou_order_settle

    oid = order.OrderID
    if not team_should_split(order):
        return False

    rec = TeamRecruit.objects.select_for_update().filter(
        order_id=oid, status=TeamRecruit.STATUS_LOCKED,
    ).first()
    if not rec:
        return False

    members = list(
        TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE)
    )
    if not members:
        return False

    c = _d(order.PlayerCommission)
    # 若份额未写齐，按锁定规则重算
    if any(_d(m.share_amount) <= 0 and m.role == TeamMember.ROLE_MEMBER for m in members) or \
       sum((_d(m.share_amount) for m in members), Decimal('0')) <= 0:
        mate_uids = [m.user_uid for m in members if m.role == TeamMember.ROLE_MEMBER]
        shares = calc_lock_shares(c, rec.share_mode, rec.per_person_amount, rec.leader_uid, mate_uids)
        for m in members:
            m.share_amount = shares.get(m.user_uid, Decimal('0.00'))
            m.save(update_fields=['share_amount', 'UpdateTime'])

    total = sum((_d(m.share_amount) for m in members), Decimal('0.00'))
    if total != c:
        # 尾差修正到队长
        diff = _d(c - total)
        for m in members:
            if m.role == TeamMember.ROLE_LEADER:
                m.share_amount = _d(m.share_amount) + diff
                m.save(update_fields=['share_amount', 'UpdateTime'])
                break

    for m in members:
        amt = _d(m.share_amount)
        biz_id = f'{oid}:team:{m.user_uid}'
        if TeamSettleLedger.query.filter(biz_id=biz_id).exists():
            continue
        try:
            user = User.query.get(UserUID=m.user_uid)
            profile = user.DashouProfile
        except Exception:
            logger.error('组队分账打手不存在 %s %s', oid, m.user_uid)
            continue
        credit_dashou_order_settle(
            order=order,
            dashou_profile=profile,
            user_id=m.user_uid,
            amount=amt,
            set_idle_status=True,
            increment_chengjiao=True,
            settle_biz_id=biz_id,
        )
        TeamSettleLedger(
            club_id=rec.club_id,
            order_id=oid,
            user_uid=m.user_uid,
            amount=amt,
            biz_id=biz_id,
        ).save()

    now = timezone.now()
    rec.status = TeamRecruit.STATUS_FINISHED
    rec.closed_at = now
    rec.save(update_fields=['status', 'closed_at', 'UpdateTime'])
    _log(rec.club_id, oid, rec.id, 'system', 'settle', f'total={c}')
    return True


def _team_members_for_penalty_split(order_id: str):
    """
    取本单应均摊罚款的组队成员。
    含招募中/已锁定/已结束（商家多在结单后申请罚款；结单会把招募置为已结束）。
    取消/作废的组队不均摊。
    """
    from orders.models import TeamRecruit, TeamMember, TeamSettleLedger

    oid = (order_id or '').strip()
    if not oid:
        return None, []
    rec = (
        TeamRecruit.query.filter(
            order_id=oid,
            status__in=[
                TeamRecruit.STATUS_RECRUITING,
                TeamRecruit.STATUS_LOCKED,
                TeamRecruit.STATUS_FINISHED,
            ],
        )
        .order_by('-id')
        .first()
    )
    if not rec:
        return None, []
    members = list(
        TeamMember.query.filter(recruit_id=rec.id, join_status=TeamMember.JOIN_ACTIVE)
    )
    # 结单后偶发清退：用同单结算流水还原参与人
    if len(members) < 2 and int(rec.status or 0) == TeamRecruit.STATUS_FINISHED:
        ledger_uids = list(
            TeamSettleLedger.query.filter(order_id=oid)
            .values_list('user_uid', flat=True)
            .distinct()
        )
        if len(ledger_uids) >= 2:
            members = list(TeamMember.query.filter(recruit_id=rec.id, user_uid__in=ledger_uids))
            if len(members) < 2:
                # 无成员行时，用流水 UID 构造轻量对象供均摊
                class _M:
                    def __init__(self, uid, role):
                        self.user_uid = uid
                        self.role = role
                leader = rec.leader_uid or ''
                members = [
                    _M(u, TeamMember.ROLE_LEADER if u == leader else TeamMember.ROLE_MEMBER)
                    for u in ledger_uids
                ]
    return rec, members


def maybe_split_penalties(*, order_id: str, total_amount, reason: str, applicant_id, applicant_identity,
                          affects_grabbing=1, club_id='', extra_fields=None) -> Optional[list]:
    """
    若有组队且至少 2 人在队（含结单后已结束），为每人创建罚单并返回列表；否则返回 None 走原逻辑。
    """
    from orders.models import Penalty
    from decimal import Decimal as D

    rec, members = _team_members_for_penalty_split(order_id)
    if not rec or len(members) < 2:
        return None

    total = _d(total_amount)
    k = len(members)
    # 队长优先拿尾差
    members.sort(key=lambda m: 0 if getattr(m, 'role', '') == 'leader' else 1)
    base = (total / D(k)).quantize(MONEY, rounding=ROUND_DOWN)
    amounts = [base] * k
    amounts[0] = _d(total - base * (k - 1))

    reason_text = f'【组队均摊】{reason or ""}'
    created = []
    # 罚单归属订单俱乐部，避免跨店接单时记到错误店；利率仍按申请人俱乐部在 lock_penalty_bonus 锁定
    order_club = ''
    try:
        from orders.models import Order
        o = Order.query.filter(OrderID=order_id).only('ClubID').first()
        order_club = (getattr(o, 'ClubID', None) or '').strip() if o else ''
    except Exception:
        order_club = ''
    cid = (order_club or club_id or rec.club_id or 'xq')[:16]
    for i, m in enumerate(members):
        p = Penalty(
            PenalizedUserID=m.user_uid,
            ApplicantID=applicant_id,
            Identity=1,
            Reason=reason_text,
            FineAmount=amounts[i],
            RelatedOrderID=order_id,
            Status=1,
            AffectsGrabbing=affects_grabbing,
            ApplicantIdentity=applicant_identity,
            ClubID=cid,
        )
        p.save()
        created.append(p)
    # 调用方负责 lock_penalty_bonus / 证据图；此处只创建罚单记录
    _log(rec.club_id, order_id, rec.id, applicant_id or '', 'penalty_split', f'n={k} total={total}')
    return created


def serialize_recruit_public(rec, order=None, *, include_game_ids=False) -> dict:
    """大厅公开字段；游戏ID默认不返回。"""
    from orders.models import Order, MerchantOrderExt
    from jituan.services.order_deal import order_auto_settle_payload
    from jituan.models import DashouDealStat
    from users.business_models import User
    from backend.utils import fmt_datetime

    order = order or Order.query.filter(OrderID=rec.order_id).first()
    need = int(rec.need_count or 0)
    joined = int(rec.joined_count or 0)
    data = {
        'recruit_id': rec.id,
        'order_id': rec.order_id,
        'club_id': rec.club_id,
        'status': rec.status,
        'need_count': need,
        'joined_count': joined,
        'remain_count': max(0, need - joined),
        'share_mode': rec.share_mode,
        'display_share_amount': float(_d(rec.display_share_amount)),
        'per_person_amount': float(_d(rec.per_person_amount)),
        'remark': rec.remark or '',
        'leader_uid': rec.leader_uid,
        'leader_nicheng': '',
        'min_deal_rate': float(rec.min_deal_rate) if rec.min_deal_rate is not None else None,
        'max_fine_rate': float(rec.max_fine_rate) if rec.max_fine_rate is not None else None,
        'opened_at': fmt_datetime(rec.opened_at) if rec.opened_at else '',
        'locked_at': fmt_datetime(rec.locked_at) if rec.locked_at else '',
        'fadanpingtai': getattr(order, 'Platform', None) if order else None,
        'fadanpingtai_text': '',
        'leixing_id': getattr(order, 'ProductTypeID', None) if order else None,
        'jieshao': (order.Description or '') if order else '',
        'tupian': (order.ImageURL or '') if order else '',
        'order_zhuangtai': getattr(order, 'Status', None) if order else None,
        'shangjia_id': '',
        'shangjia_nicheng': '',
        'sj_avatar': '',
        'leader_avatar': '',
        'leader_deal_stat': None,
        'shangjia_deal_stat': None,
    }
    if order:
        data['fadanpingtai_text'] = '平台单' if order.Platform == 1 else ('商家单' if order.Platform == 2 else '')
        if order.Platform == 2:
            try:
                ext = order.shangjia_kuozhan
                sj_uid = (ext.MerchantID or '').strip()
                data['shangjia_id'] = sj_uid
                data['shangjia_nicheng'] = ext.MerchantNickname or ''
                # 对齐抢单池：商家头像取 User.Avatar
                if sj_uid:
                    try:
                        sj_user = User.query.filter(UserUID=sj_uid).first()
                        if sj_user:
                            data['sj_avatar'] = getattr(sj_user, 'Avatar', None) or ''
                            if not data['shangjia_nicheng']:
                                try:
                                    boss = getattr(sj_user, 'BossProfile', None)
                                    if boss and getattr(boss, 'nickname', None):
                                        data['shangjia_nicheng'] = boss.nickname
                                except Exception:
                                    pass
                    except Exception:
                        pass
            except Exception:
                pass
        # 自动结算字段：仅锁定且订单结算中
        if rec.status == 2 and int(order.Status or 0) == 8:
            data.update(order_auto_settle_payload(order))

    try:
        u = User.query.filter(UserUID=rec.leader_uid).select_related('DashouProfile').first()
        if u:
            data['leader_avatar'] = getattr(u, 'Avatar', None) or ''
            if getattr(u, 'DashouProfile', None):
                data['leader_nicheng'] = u.DashouProfile.nicheng or ''
    except Exception:
        pass

    # 队长打手结算率/被罚款率（有样本才返回，与订单列表口径一致）
    from jituan.services.deal_stats import miniapp_dashou_deal_payload
    leader_uid = (rec.leader_uid or '').strip()
    club_ids = []
    for cid in (
        (rec.club_id or '').strip(),
        (getattr(order, 'ClubID', None) or '').strip() if order else '',
    ):
        if cid and cid not in club_ids:
            club_ids.append(cid)
    dashou_row = None
    if leader_uid and club_ids:
        for cid in club_ids:
            dashou_row = DashouDealStat.query.filter(club_id=cid, dashou_uid=leader_uid).first()
            if dashou_row and int(dashou_row.order_count or 0) > 0:
                break
            dashou_row = None
    data['leader_deal_stat'] = miniapp_dashou_deal_payload(dashou_row)

    # 商家单：附带商家成交率/罚款率/均时（有样本才有；前端有哪个展示哪个）
    try:
        from jituan.services.deal_stats import miniapp_merchant_deal_payload
        from jituan.models import MerchantDealStat
        sj_uid = (data.get('shangjia_id') or '').strip()
        if sj_uid and club_ids:
            m_row = None
            for cid in club_ids:
                m_row = MerchantDealStat.query.filter(club_id=cid, merchant_uid=sj_uid).first()
                if m_row and int(m_row.order_count or 0) > 0:
                    break
                m_row = None
            data['shangjia_deal_stat'] = miniapp_merchant_deal_payload(m_row)
    except Exception:
        data['shangjia_deal_stat'] = None

    if include_game_ids:
        from orders.models import TeamMember
        from users.business_models import User
        mems = list(TeamMember.query.filter(recruit_id=rec.id, join_status=1))
        uids = [m.user_uid for m in mems]
        nick_map = {}
        if uids:
            for u in User.query.filter(UserUID__in=uids).select_related('DashouProfile'):
                try:
                    nick_map[u.UserUID] = (u.DashouProfile.nicheng or '') if getattr(u, 'DashouProfile', None) else ''
                except Exception:
                    nick_map[u.UserUID] = ''
        data['members'] = [
            {
                'user_uid': m.user_uid,
                'role': m.role,
                'role_text': '队长' if m.role == 'leader' else '队友',
                'nicheng': nick_map.get(m.user_uid, ''),
                'game_id': m.game_id,
                'share_amount': float(_d(m.share_amount)),
            }
            for m in mems
        ]
    return data


def batch_team_list_flags(order_ids: list, viewer_uid: str = '') -> dict:
    """订单列表用：{order_id: {has_team, team_status, ...}}，不含游戏ID。"""
    from orders.models import TeamRecruit, TeamMember
    if not order_ids:
        return {}
    recs = list(
        TeamRecruit.query.filter(
            order_id__in=list(order_ids),
            status__in=[
                TeamRecruit.STATUS_RECRUITING,
                TeamRecruit.STATUS_LOCKED,
                TeamRecruit.STATUS_FINISHED,
            ],
        )
    )
    if not recs:
        return {}
    # 一单一帖取最新
    best = {}
    for r in recs:
        prev = best.get(r.order_id)
        if not prev or r.id > prev.id:
            best[r.order_id] = r
    rids = [r.id for r in best.values()]
    my_roles = {}
    if viewer_uid and rids:
        for m in TeamMember.query.filter(
            recruit_id__in=rids, user_uid=viewer_uid, join_status=TeamMember.JOIN_ACTIVE,
        ):
            my_roles[m.recruit_id] = m.role
    out = {}
    status_text = {1: '招募中', 2: '已锁定', 3: '已结束'}
    for oid, r in best.items():
        role = my_roles.get(r.id) or ('leader' if viewer_uid and r.leader_uid == viewer_uid else '')
        out[oid] = {
            'has_team': True,
            'team_status': r.status,
            'team_status_text': status_text.get(r.status, ''),
            'team_joined': int(r.joined_count or 0),
            'team_need': int(r.need_count or 0),
            'team_role': role,
            'team_role_text': '队长' if role == 'leader' else ('队友' if role == 'member' else ''),
        }
    return out
