"""
全面审计会员 sync Bug 影响范围（与 rollback 的 44 条不是同一口径）。

rollback 只收「当前仍错误有效」的记录。
本命令额外统计：
- 全库会员/充值单规模
- 当前仍有效 vs 充值单原定期限已过（= rollback 第二类）
- 疑似被 repair 写回过（含已再次过期、当前看不出的历史受害）

用法：
  python manage.py audit_member_bug_impact
  python manage.py audit_member_bug_impact --since 2026-06-15
"""
from datetime import timedelta

from django.core.management.base import BaseCommand
from django.utils import timezone

from jituan.services.huiyuan_bundle import (
    _normalize_datetime_for_compare,
    huiyuan_ids_match,
)
from jituan.services.member_recharge import (
    MEMBER_LEIXING,
    MEMBER_PAID_STATUS,
    _czjilu_entitlement_end,
    _czjilu_entitlement_revoked,
    _parse_since_option,
    club_huiyuan_sellable,
    _order_is_trial_like,
)
from products.models import Czjilu, Huiyuangoumai
from users.models import Xiugaijilu


def _order_purchase_days(order, club_id=None):
    cid = getattr(order, 'club_id', None) or club_id or 'xq'
    is_trial = _order_is_trial_like(order, cid)
    days = int(getattr(order, 'purchase_days', None) or 0)
    if days <= 0:
        _, _, days = club_huiyuan_sellable(cid, order.huiyuan_id, is_trial=is_trial)
    return max(int(days or 0), 1)


def _paid_orders_for(yonghu_id, huiyuan_id):
    return [
        o for o in Czjilu.query.filter(
            yonghuid=yonghu_id,
            leixing=MEMBER_LEIXING,
            zhuangtai=MEMBER_PAID_STATUS,
        )
        if huiyuan_ids_match(o.huiyuan_id, huiyuan_id)
    ]


def _suspected_repair_restore(rec, since=None):
    """
    repair 特征：UpdateTime 时点将 daoqi 设为 UpdateTime+days，
    且存在匹配充值单原定期限在 UpdateTime 之前已结束。
    """
    updated = _normalize_datetime_for_compare(rec.UpdateTime)
    since_dt = _parse_since_option(since)
    if since_dt and updated < since_dt:
        return None
    daoqi = _normalize_datetime_for_compare(rec.daoqi_time)
    gap = daoqi - updated
    gap_days = gap.days
    if gap_days < 1 or gap_days > 400:
        return None

    orders = _paid_orders_for(rec.yonghu_id, rec.huiyuan_id)
    if not orders:
        return None

    for order in orders:
        if _czjilu_entitlement_revoked(order):
            continue
        days = _order_purchase_days(order, rec.club_id)
        if abs(days - gap_days) > 2:
            continue
        end = _czjilu_entitlement_end(order, rec.club_id)
        if end < updated:
            return order.dingdan_id
    return None


class Command(BaseCommand):
    help = '审计会员 sync Bug 影响规模（全库口径 vs rollback 口径）'

    def add_arguments(self, parser):
        parser.add_argument(
            '--since',
            default='2026-06-01',
            help='疑似 repair 的 UpdateTime 起始日（默认 2026-06-01）',
        )
        parser.add_argument(
            '--sample',
            type=int,
            default=30,
            help='每类样例输出条数',
        )

    def handle(self, *args, **options):
        since = options['since']
        sample_n = int(options['sample'] or 30)
        now = _normalize_datetime_for_compare(timezone.now())

        total_hy = Huiyuangoumai.query.count()
        active_hy = Huiyuangoumai.query.filter(
            huiyuan_zhuangtai=1,
            daoqi_time__gt=timezone.now(),
        ).count()

        expired_czjilu_rows = 0
        expired_czjilu_users = set()
        for o in Czjilu.query.filter(leixing=MEMBER_LEIXING, zhuangtai=MEMBER_PAID_STATUS):
            end = _czjilu_entitlement_end(o)
            if end <= now:
                expired_czjilu_rows += 1
                expired_czjilu_users.add(o.yonghuid)

        rollback_active_orphan = 0
        rollback_admin_still_active = 0
        suspected_all = []
        suspected_still_active = []
        suspected_already_expired = []

        seen_admin = set()
        for log in Xiugaijilu.query.filter(huiyuan_id__isnull=False).exclude(huiyuan_id=''):
            note = log.qitashuoming or ''
            if '移除' not in note and '减会员' not in note:
                continue
            if not log.yonghuid or not log.huiyuan_id:
                continue
            pair = (log.yonghuid, str(log.huiyuan_id).strip())
            if pair in seen_admin:
                continue
            seen_admin.add(pair)
            for rec in Huiyuangoumai.query.filter(yonghu_id=log.yonghuid):
                if not huiyuan_ids_match(rec.huiyuan_id, log.huiyuan_id):
                    continue
                if not rec.jiance_shifou_daoqi() and rec.huiyuan_zhuangtai == 1:
                    rollback_admin_still_active += 1
                    break

        for rec in Huiyuangoumai.query.all():
            is_active = (
                rec.huiyuan_zhuangtai == 1
                and _normalize_datetime_for_compare(rec.daoqi_time) > now
            )

            paid = _paid_orders_for(rec.yonghu_id, rec.huiyuan_id)
            if paid and is_active:
                has_live_czjilu = any(
                    not _czjilu_entitlement_revoked(o)
                    and _czjilu_entitlement_end(o, rec.club_id) > now
                    for o in paid
                )
                if not has_live_czjilu:
                    rollback_active_orphan += 1

            dingdan_id = _suspected_repair_restore(rec, since=since)
            if dingdan_id:
                row = (
                    rec.yonghu_id,
                    rec.huiyuan_id,
                    str(rec.daoqi_time),
                    str(rec.UpdateTime),
                    dingdan_id,
                    '仍有效' if is_active else '已再次过期',
                )
                suspected_all.append(row)
                if is_active:
                    suspected_still_active.append(row)
                else:
                    suspected_already_expired.append(row)

        self.stdout.write(self.style.WARNING('=== 全库规模（回答「几万条过期充值」）==='))
        self.stdout.write(f'  huiyuangoumai 总记录数: {total_hy}')
        self.stdout.write(f'  当前仍有效会员记录: {active_hy}')
        self.stdout.write(
            f'  已付会员充值单且原定期限已过: {expired_czjilu_rows} 条，'
            f'{len(expired_czjilu_users)} 个用户',
        )

        self.stdout.write(self.style.WARNING(
            '\n=== rollback 命令口径（只收「当前仍错误有效」）===',
        ))
        self.stdout.write(f'  后台已减仍有效: {rollback_admin_still_active}')
        self.stdout.write(f'  充值单全过期但仍有效: {rollback_active_orphan}')
        self.stdout.write(
            f'  合计约: {rollback_admin_still_active + rollback_active_orphan} 条',
        )

        self.stdout.write(self.style.WARNING(
            f'\n=== 更广口径：疑似 repair 写回过（UpdateTime>={since}）===',
        ))
        self.stdout.write(f'  疑似受害记录总数: {len(suspected_all)}')
        self.stdout.write(f'    其中当前仍有效: {len(suspected_still_active)}')
        self.stdout.write(f'    其中已再次过期（rollback 收不到）: {len(suspected_already_expired)}')

        unique_users = {r[0] for r in suspected_all}
        self.stdout.write(f'  涉及不同用户数: {len(unique_users)}')

        self.stdout.write('\n样例（疑似 repair，含已再次过期）:')
        for row in suspected_all[:sample_n]:
            self.stdout.write(
                f'  用户{row[0]} 会员{row[1]} {row[5]} '
                f'到期{row[2]} 更新{row[3]} 依据单{row[4]}',
            )
        if len(suspected_all) > sample_n:
            self.stdout.write(f'  ... 另有 {len(suspected_all) - sample_n} 条')

        self.stdout.write(self.style.SUCCESS(
            '\n说明: 几万条过期充值单是历史正常数据；Bug 不会批量改全库，'
            '只在用户打开抢单池时触发。rollback 只处理「此刻仍错误有效」的记录。',
        ))
