"""成交/罚款指标幂等记账（仅 stats_enabled 且 CreateTime>=stats_since）。"""
from __future__ import annotations

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

logger = logging.getLogger(__name__)


def _cfg(club_id: str):
    from jituan.services.order_deal import get_or_create_deal_config
    return get_or_create_deal_config(club_id)


def _stats_on(cfg, order) -> bool:
    """
    成交指标始终记账（抢单池要展示成交率/罚款率/均时）。
    若配置了 stats_since，只统计该时间点之后的订单。
    """
    if not order:
        return False
    since = getattr(cfg, 'stats_since', None) if cfg else None
    if since:
        ct = getattr(order, 'CreateTime', None)
        if ct and ct < since:
            return False
    return True


def ensure_club_stats_bootstrapped(club_id: str, *, lookback_days: int = 180):
    """
    俱乐部首次拉取抢单池商家指标时，若从未记账则回填近 N 天历史订单。
    幂等：用 DealStatEvent 打标记。
    """
    cid = (club_id or '').strip()
    if not cid:
        return
    marker = f'{cid}:stats_bootstrap_v1'
    from jituan.models import DealStatEvent, MerchantDealStat
    if DealStatEvent.query.filter(event_key=marker).exists():
        return
    # 已有商家样本则只打标，避免重复重算
    if MerchantDealStat.query.filter(club_id=cid, order_count__gt=0).exists():
        _claim_event(cid, marker, 'stats_bootstrap', subject_uid='system')
        return
    try:
        from orders.models import Order
        from datetime import timedelta
        since = timezone.now() - timedelta(days=max(7, int(lookback_days or 180)))
        qs = (
            Order.query.filter(ClubID=cid, CreateTime__gte=since)
            .exclude(PlayerID__isnull=True)
            .exclude(PlayerID='')
            .filter(Status__in=[2, 3, 4, 5, 6, 8])
            .order_by('CreateTime')[:800]
        )
        for order in qs:
            try:
                on_order_enter_pool(order, force=True)
                if int(order.Status or 0) == 3:
                    on_order_completed(order, settle_started_at=getattr(order, 'SettlementTime', None), force=True)
                elif int(order.Status or 0) == 5:
                    on_order_refunded(order, force=True)
            except Exception as e:
                logger.warning('回填单条成交指标失败 order=%s: %s', getattr(order, 'OrderID', ''), e)
        _claim_event(cid, marker, 'stats_bootstrap', subject_uid='system', meta={'n': len(qs)})
        logger.info('俱乐部 %s 成交指标回填完成 n=%s', cid, len(qs))
    except Exception as e:
        logger.error('俱乐部成交指标回填失败 club=%s: %s', cid, e, exc_info=True)


def _claim_event(club_id: str, event_key: str, event_type: str, order_id='', subject_uid='', meta=None) -> bool:
    from jituan.models import DealStatEvent
    if DealStatEvent.query.filter(event_key=event_key).exists():
        return False
    try:
        DealStatEvent.objects.create(
            club_id=club_id,
            event_key=event_key[:96],
            event_type=event_type[:32],
            order_id=(order_id or '')[:32],
            subject_uid=(subject_uid or '')[:16],
            meta_json=meta or {},
        )
        return True
    except Exception:
        return False


def _ensure_merchant(club_id, uid):
    from jituan.models import MerchantDealStat
    from django.db import IntegrityError
    row = MerchantDealStat.query.filter(club_id=club_id, merchant_uid=uid).first()
    if row:
        return row
    try:
        row = MerchantDealStat(club_id=club_id, merchant_uid=uid)
        row.save()
        return row
    except IntegrityError:
        # 并发回填/记账时可能已有同 club+merchant 行，读回即可
        return MerchantDealStat.query.filter(club_id=club_id, merchant_uid=uid).first()


def _ensure_dashou(club_id, uid):
    from jituan.models import DashouDealStat
    from django.db import IntegrityError
    row = DashouDealStat.query.filter(club_id=club_id, dashou_uid=uid).first()
    if row:
        return row
    try:
        row = DashouDealStat(club_id=club_id, dashou_uid=uid)
        row.save()
        return row
    except IntegrityError:
        return DashouDealStat.query.filter(club_id=club_id, dashou_uid=uid).first()


def _merchant_uid(order):
    try:
        ext = getattr(order, 'shangjia_kuozhan', None)
        return (getattr(ext, 'MerchantID', None) or '') if ext else ''
    except Exception:
        return ''


@transaction.atomic
def on_order_enter_pool(order, *, force=False):
    """接单入池：商家/打手 order_count+1。force=True 用于历史回填（忽略 since）。"""
    club_id = (getattr(order, 'ClubID', None) or '').strip()
    cfg = _cfg(club_id)
    if not force and not _stats_on(cfg, order):
        return
    oid = order.OrderID
    dashou = (order.PlayerID or '').strip()
    if dashou and _claim_event(club_id, f'{oid}:dashou_in', 'order_in_pool', oid, dashou):
        _ensure_dashou(club_id, dashou)
        from jituan.models import DashouDealStat
        DashouDealStat.objects.filter(club_id=club_id, dashou_uid=dashou).update(
            order_count=F('order_count') + 1, UpdateTime=timezone.now(),
        )
    if order.Platform == 2:
        mid = _merchant_uid(order)
        if mid and _claim_event(club_id, f'{oid}:merchant_in', 'order_in_pool', oid, mid):
            _ensure_merchant(club_id, mid)
            from jituan.models import MerchantDealStat
            MerchantDealStat.objects.filter(club_id=club_id, merchant_uid=mid).update(
                order_count=F('order_count') + 1, UpdateTime=timezone.now(),
            )


@transaction.atomic
def on_order_completed(order, settle_started_at=None, *, force=False):
    club_id = (getattr(order, 'ClubID', None) or '').strip()
    cfg = _cfg(club_id)
    if not force and not _stats_on(cfg, order):
        return
    oid = order.OrderID
    if not _claim_event(club_id, f'{oid}:completed', 'completed', oid):
        return
    # 确保入池
    on_order_enter_pool(order, force=force)

    st = settle_started_at or getattr(order, '_deal_settle_started_at', None)
    duration = None
    if st:
        try:
            duration = max(0, int((timezone.now() - st).total_seconds()))
        except Exception:
            duration = None

    dashou = (order.PlayerID or '').strip()
    if dashou:
        _ensure_dashou(club_id, dashou)
        from jituan.models import DashouDealStat
        DashouDealStat.objects.filter(club_id=club_id, dashou_uid=dashou).update(
            completed_count=F('completed_count') + 1, UpdateTime=timezone.now(),
        )
    if order.Platform == 2:
        mid = _merchant_uid(order)
        if mid:
            _ensure_merchant(club_id, mid)
            from jituan.models import MerchantDealStat
            qs = MerchantDealStat.objects.filter(club_id=club_id, merchant_uid=mid)
            qs.update(completed_count=F('completed_count') + 1, UpdateTime=timezone.now())
            if duration is not None:
                qs.update(
                    settle_duration_sum_seconds=F('settle_duration_sum_seconds') + duration,
                    settle_duration_sample_count=F('settle_duration_sample_count') + 1,
                )


@transaction.atomic
def on_order_refunded(order, *, force=False):
    club_id = (getattr(order, 'ClubID', None) or '').strip()
    cfg = _cfg(club_id)
    if (not force and not _stats_on(cfg, order)) or order.Platform != 2:
        return
    oid = order.OrderID
    mid = _merchant_uid(order)
    if not mid:
        return
    if not _claim_event(club_id, f'{oid}:refunded', 'refunded', oid, mid):
        return
    on_order_enter_pool(order, force=force)
    _ensure_merchant(club_id, mid)
    from jituan.models import MerchantDealStat
    MerchantDealStat.objects.filter(club_id=club_id, merchant_uid=mid).update(
        refund_count=F('refund_count') + 1, UpdateTime=timezone.now(),
    )


@transaction.atomic
def on_penalty_paid(penalty):
    """罚单 Status→2 已缴纳。"""
    from utils.penalty_status import PENALTY_PAID
    if int(getattr(penalty, 'Status', 0) or 0) != PENALTY_PAID:
        return
    club_id = (getattr(penalty, 'ClubID', None) or '').strip()
    if not club_id:
        return
    pid = getattr(penalty, 'id', None) or getattr(penalty, 'pk', None)
    key = f'penalty:{pid}:paid'
    if not _claim_event(club_id, key, 'fine_paid', getattr(penalty, 'RelatedOrderID', '') or ''):
        return
    applicant = (getattr(penalty, 'ApplicantID', None) or '').strip()
    penalized = (getattr(penalty, 'PenalizedUserID', None) or '').strip()
    # 商家申请人 → 商家罚款率分子
    if applicant:
        _ensure_merchant(club_id, applicant)
        from jituan.models import MerchantDealStat
        MerchantDealStat.objects.filter(club_id=club_id, merchant_uid=applicant).update(
            fine_paid_count=F('fine_paid_count') + 1, UpdateTime=timezone.now(),
        )
    # 被罚打手 → 被罚款率
    if penalized:
        _ensure_dashou(club_id, penalized)
        from jituan.models import DashouDealStat
        DashouDealStat.objects.filter(club_id=club_id, dashou_uid=penalized).update(
            fine_paid_count=F('fine_paid_count') + 1, UpdateTime=timezone.now(),
        )


def serialize_merchant_stat(row) -> dict:
    n = int(row.order_count or 0)
    c = int(row.completed_count or 0)
    r = int(row.refund_count or 0)
    f = int(row.fine_paid_count or 0)
    sc = int(row.settle_duration_sample_count or 0)
    ss = int(row.settle_duration_sum_seconds or 0)
    return {
        'merchant_uid': row.merchant_uid,
        'order_count': n,
        'completed_count': c,
        'refund_count': r,
        'fine_paid_count': f,
        'deal_rate': round(c / n, 6) if n else 0,
        'refund_rate': round(r / n, 6) if n else 0,
        'fine_rate': round(f / n, 6) if n else 0,
        'avg_deal_hours': round((ss / sc) / 3600.0, 4) if sc else 0,
        'settle_duration_sample_count': sc,
    }


def pool_merchant_stat_payload(row) -> dict | None:
    """
    抢单池展示用：有订单样本才返回。
    成交率/罚款率有样本即返回；均时仅有结算样本时返回（含均时≈0）。
    前端按字段有哪个展示哪个。
    """
    if not row:
        return None
    n = int(row.order_count or 0)
    if n <= 0:
        return None
    full = serialize_merchant_stat(row)
    out = {
        'deal_rate': full['deal_rate'],
        'fine_rate': full['fine_rate'],
        'deal_rate_text': f"{round(full['deal_rate'] * 100, 1)}%",
        'fine_rate_text': f"{round(full['fine_rate'] * 100, 1)}%",
    }
    sc = int(full.get('settle_duration_sample_count') or 0)
    if sc > 0:
        h = float(full.get('avg_deal_hours') or 0)
        out['avg_deal_hours'] = h
        if h < 1:
            out['avg_deal_hours_text'] = f'{max(1, int(round(h * 60)))}分钟' if h > 0 else '不足1分钟'
        else:
            out['avg_deal_hours_text'] = f'{round(h, 1)}小时'
    return out


def batch_pool_merchant_stats(club_id: str, merchant_uids, order_club_map=None) -> dict:
    """
    批量取商家抢单池指标。
    order_club_map: {merchant_uid: order_club_id} 优先用订单所属俱乐部查指标。
    返回 {merchant_uid: payload}
    """
    from jituan.models import MerchantDealStat
    uids = [str(u).strip() for u in (merchant_uids or []) if u and str(u).strip()]
    if not uids:
        return {}
    default_club = (club_id or '').strip()
    # 首次无样本时回填历史，保证旧单刷新也能看到成交率/罚款率/均时
    try:
        if default_club:
            ensure_club_stats_bootstrapped(default_club)
        if order_club_map:
            for cid in set((c or '').strip() for c in order_club_map.values() if c):
                if cid and cid != default_club:
                    ensure_club_stats_bootstrapped(cid)
    except Exception as e:
        logger.warning('抢单池成交指标回填触发失败: %s', e)
    club_ids = {default_club} if default_club else set()
    if order_club_map:
        for cid in order_club_map.values():
            c = (cid or '').strip()
            if c:
                club_ids.add(c)
    if not club_ids:
        return {}
    rows = list(
        MerchantDealStat.query.filter(
            club_id__in=list(club_ids),
            merchant_uid__in=uids,
        )
    )
    by_key = {(r.club_id, r.merchant_uid): r for r in rows}
    out = {}
    missing = []
    for uid in uids:
        prefer = ((order_club_map or {}).get(uid) or default_club or '').strip()
        row = by_key.get((prefer, uid))
        if not row:
            for cid in club_ids:
                row = by_key.get((cid, uid))
                if row:
                    break
        if not row:
            missing.append(uid)
            continue
        payload = pool_merchant_stat_payload(row)
        if payload:
            out[uid] = payload
    # 订单俱乐部对不上时，按商家 UID 兜底（取样本最多的一条）
    if missing:
        for r in MerchantDealStat.query.filter(merchant_uid__in=missing).order_by('-order_count'):
            uid = r.merchant_uid
            if uid in out:
                continue
            payload = pool_merchant_stat_payload(r)
            if payload:
                out[uid] = payload
    return out


def serialize_dashou_stat(row) -> dict:
    n = int(row.order_count or 0)
    c = int(row.completed_count or 0)
    f = int(row.fine_paid_count or 0)
    return {
        'dashou_uid': row.dashou_uid,
        'order_count': n,
        'completed_count': c,
        'fine_paid_count': f,
        'deal_rate': round(c / n, 6) if n else 0,
        'fined_rate': round(f / n, 6) if n else 0,
    }


def miniapp_merchant_deal_payload(row) -> dict | None:
    """小程序商家订单列表顶：有样本才返回；文案字段供前端标「结算率」。"""
    if not row:
        return None
    n = int(row.order_count or 0)
    if n <= 0:
        return None
    full = serialize_merchant_stat(row)
    out = {
        'role': 'merchant',
        'order_count': full['order_count'],
        'deal_rate': full['deal_rate'],
        'fine_rate': full['fine_rate'],
        'deal_rate_text': f"{round(full['deal_rate'] * 100, 1)}%",
        'fine_rate_text': f"{round(full['fine_rate'] * 100, 1)}%",
    }
    sc = int(full.get('settle_duration_sample_count') or 0)
    if sc > 0:
        h = float(full.get('avg_deal_hours') or 0)
        out['avg_deal_hours'] = h
        if h < 1:
            out['avg_deal_hours_text'] = f'{max(1, int(round(h * 60)))}分钟' if h > 0 else '不足1分钟'
        else:
            out['avg_deal_hours_text'] = f'{round(h, 1)}小时'
    return out


def miniapp_dashou_deal_payload(row) -> dict | None:
    """小程序打手订单列表顶：有样本才返回。"""
    if not row:
        return None
    n = int(row.order_count or 0)
    if n <= 0:
        return None
    full = serialize_dashou_stat(row)
    return {
        'role': 'dashou',
        'order_count': full['order_count'],
        'deal_rate': full['deal_rate'],
        'fined_rate': full['fined_rate'],
        'deal_rate_text': f"{round(full['deal_rate'] * 100, 1)}%",
        'fined_rate_text': f"{round(full['fined_rate'] * 100, 1)}%",
    }
