"""后台会员管理：全局 Huiyuan 定义 + 各俱乐部 ClubHuiyuanPrice 售价/分成/体验配置。"""
import logging
import random
import string
from decimal import Decimal

from django.db import transaction, IntegrityError

from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL
from jituan.models import ClubHuiyuanPrice
from jituan.services.club_context import club_id_for_write, resolve_club_id_from_request, resolve_club_scope
from jituan.services.club_user_access import list_response_meta
from products.models import Huiyuan

logger = logging.getLogger(__name__)

CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图，请先在顶栏切换到具体俱乐部后再操作'


def club_write_blocked(request):
    return resolve_club_scope(request) == DATA_SCOPE_ALL


def _price_row_to_dict(row):
    if not row:
        return {}
    return {
        'formal_days': int(row.formal_days or 30),
        'trial_enabled': bool(row.trial_enabled),
        'trial_price': str(row.trial_price or 0),
        'trial_days': int(row.trial_days or 0),
        'trial_guanshifc': str(row.trial_guanshifc or 0),
        'trial_zuzhangfc': str(row.trial_zuzhangfc or 0),
        'card_image': (row.card_image or '').strip(),
        'card_bg': (row.card_bg or '').strip(),
    }


def _format_member(m, price_row=None, club_id=None):
    global_jiage = str(m.jiage)
    global_guanshifc = str(m.guanshifc)
    global_zuzhangfc = str(m.zuzhangfc)
    item = {
        'huiyuan_id': m.huiyuan_id,
        'jieshao': m.jieshao,
        'jtjieshao': m.jtjieshao,
        'jiage': global_jiage,
        'guanshifc': global_guanshifc,
        'zuzhangfc': global_zuzhangfc,
        'global_jiage': global_jiage,
        'global_guanshifc': global_guanshifc,
        'global_zuzhangfc': global_zuzhangfc,
        'goumai_cishu': m.goumai_cishu,
        'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None,
        'UpdateTime': m.UpdateTime.isoformat() if m.UpdateTime else None,
        'bankuai_id': m.bankuai_id,
        'club_enabled': True,
        'price_source': 'global',
        'formal_days': 30,
        'trial_enabled': False,
        'trial_price': '0.00',
        'trial_days': 0,
        'trial_guanshifc': '0.00',
        'trial_zuzhangfc': '0.00',
        'is_bundle': bool(getattr(m, 'is_bundle', False)),
        'included_huiyuan_ids': [],
    }
    if price_row is not None:
        item['jiage'] = str(price_row.jiage)
        item['guanshifc'] = str(price_row.guanshifc)
        item['zuzhangfc'] = str(price_row.zuzhangfc)
        item['club_enabled'] = bool(price_row.is_enabled)
        item['price_source'] = 'club'
        item.update(_price_row_to_dict(price_row))
    if item.get('is_bundle') and club_id:
        from jituan.services.huiyuan_bundle import get_bundle_included_ids
        item['included_huiyuan_ids'] = get_bundle_included_ids(club_id, m.huiyuan_id)
    return item


def count_club_member_sales(club_id, huiyuan_id, formal_only=True):
    """本俱乐部已支付会员单数（用于后台展示，非全局 goumai_cishu）。"""
    from products.models import Czjilu
    qs = Czjilu.query.filter(
        club_id=club_id,
        huiyuan_id=huiyuan_id,
        leixing=1,
        zhuangtai=3,
    )
    if formal_only:
        qs = qs.filter(is_trial=False)
    return qs.count()


def build_member_list_payload(request):
    scope = resolve_club_scope(request)
    club_id = resolve_club_id_from_request(request)
    # P1 优化：限制查询字段，避免加载不必要的大字段
    members = list(Huiyuan.query.all().only(
        'huiyuan_id', 'jieshao', 'jtjieshao', 'jiage',
        'guanshifc', 'zuzhangfc', 'goumai_cishu', 'bankuai_id',
        'CreateTime', 'is_bundle',
    ).order_by('-CreateTime'))
    member_list = []

    price_map = {}
    if scope != DATA_SCOPE_ALL:
        price_map = {
            r.huiyuan_id: r
            for r in ClubHuiyuanPrice.query.filter(club_id=club_id)
        }

    # P1 优化：批量预取本俱乐部各会员的已支付单数，避免循环内 N+1
    _sales_map = {}
    if scope != DATA_SCOPE_ALL and members:
        from products.models import Czjilu
        from django.db.models import Count
        _sales_rows = Czjilu.query.filter(
            club_id=club_id,
            huiyuan_id__in=[m.huiyuan_id for m in members],
            leixing=1,
            zhuangtai=3,
            is_trial=False,
        ).values('huiyuan_id').annotate(cnt=Count('id'))
        _sales_map = {r['huiyuan_id']: r['cnt'] for r in _sales_rows}

    if scope == DATA_SCOPE_ALL:
        for m in members:
            member_list.append(_format_member(m))
    else:
        # 有本俱乐部专属价：只展示本店上架项；尚无专属价：回落集团默认会员定义。
        # 绝不混入其他俱乐部的 ClubHuiyuanPrice。
        has_club_prices = bool(price_map)
        for m in members:
            row = price_map.get(m.huiyuan_id)
            if has_club_prices:
                if row is None or not row.is_enabled:
                    continue
                item = _format_member(m, row, club_id=club_id)
            else:
                item = _format_member(m, club_id=club_id)
            item['goumai_cishu'] = _sales_map.get(m.huiyuan_id, 0)
            member_list.append(item)

    return {
        'list': member_list,
        'club_id': club_id,
        'scope': scope,
        'price_note': (
            f'当前俱乐部 {club_id} 专属售价；未配置专属价时展示集团默认会员。'
            if scope != DATA_SCOPE_ALL else
            '集团汇总：展示全局默认价；切换具体俱乐部可配置售价与体验会员。'
        ),
        **list_response_meta(request),
    }


def build_member_catalog_payload(request):
    """集团会员类型 + 本俱乐部未上架的可选列表。"""
    club_id = resolve_club_id_from_request(request)
    enabled_ids = set(
        ClubHuiyuanPrice.query.filter(club_id=club_id, is_enabled=True).values_list('huiyuan_id', flat=True)
    )
    # P1 优化：DB 端排除已上架会员，限制字段，避免全表加载 + Python 过滤
    catalog_qs = Huiyuan.query.all()
    if enabled_ids:
        catalog_qs = catalog_qs.exclude(huiyuan_id__in=list(enabled_ids))
    catalog = list(
        catalog_qs.only('huiyuan_id', 'jieshao', 'jtjieshao', 'is_bundle')
        .order_by('-CreateTime')
        .values('huiyuan_id', 'jieshao', 'jtjieshao', 'is_bundle')
    )
    # values 返回 dict 列表，需补 is_bundle bool 化
    for row in catalog:
        row['is_bundle'] = bool(row.get('is_bundle'))
    return {'club_id': club_id, 'catalog': catalog}


def _parse_trial_fields(data, jiage, guanshifc, zuzhangfc):
    formal_days = int(data.get('formal_days') or 30)
    trial_enabled = bool(data.get('trial_enabled'))
    trial_price = Decimal(str(data.get('trial_price') or 0))
    trial_days = int(data.get('trial_days') or 0)
    trial_guanshifc = Decimal(str(data.get('trial_guanshifc') or 0))
    trial_zuzhangfc = Decimal(str(data.get('trial_zuzhangfc') or 0))
    if formal_days < 1 or formal_days > 3650:
        raise ValueError('正式会员天数无效')
    if trial_enabled:
        if trial_price <= 0 or trial_days <= 0:
            raise ValueError('开启体验会员须配置体验价格与天数')
        if trial_guanshifc + trial_zuzhangfc > trial_price:
            raise ValueError('体验管事+组长分成不能大于体验价格')
    if guanshifc + zuzhangfc > jiage:
        raise ValueError('管事分成+组长分成不能大于会员价格')
    return {
        'formal_days': formal_days,
        'trial_enabled': trial_enabled,
        'trial_price': trial_price,
        'trial_days': trial_days,
        'trial_guanshifc': trial_guanshifc,
        'trial_zuzhangfc': trial_zuzhangfc,
    }


def update_member_for_club(request, huiyuan_id, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, extra=None):
    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG

    club_id = club_id_for_write(request)
    extra = extra or {}

    try:
        member_preview = Huiyuan.objects.get(huiyuan_id=huiyuan_id)
        if getattr(member_preview, 'is_bundle', False):
            extra = dict(extra)
            extra['trial_enabled'] = False
    except Huiyuan.DoesNotExist:
        pass

    try:
        trial_fields = _parse_trial_fields(extra, jiage, guanshifc, zuzhangfc)
    except ValueError as e:
        return None, str(e)

    with transaction.atomic():
        try:
            member = Huiyuan.objects.select_for_update().get(huiyuan_id=huiyuan_id)
        except Huiyuan.DoesNotExist:
            return None, '会员不存在'

        member.jieshao = jieshao
        member.jtjieshao = jtjieshao
        member.save(update_fields=['jieshao', 'jtjieshao', 'UpdateTime'])

        row, created = ClubHuiyuanPrice.query.get_or_create(
            club_id=club_id,
            huiyuan_id=huiyuan_id,
            defaults={
                'jiage': jiage,
                'guanshifc': guanshifc,
                'zuzhangfc': zuzhangfc,
                'is_enabled': True,
                'bankuai_id': member.bankuai_id,
                **trial_fields,
            },
        )
        if not created:
            row.jiage = jiage
            row.guanshifc = guanshifc
            row.zuzhangfc = zuzhangfc
            row.is_enabled = True
            for k, v in trial_fields.items():
                setattr(row, k, v)
            if 'card_image' in extra:
                row.card_image = (extra.get('card_image') or '').strip()
            if 'card_bg' in extra:
                row.card_bg = (extra.get('card_bg') or '').strip()
            row.save()

        if 'included_huiyuan_ids' in extra and getattr(member, 'is_bundle', False):
            from jituan.services.huiyuan_bundle import save_bundle_includes
            _, err = save_bundle_includes(club_id, huiyuan_id, extra.get('included_huiyuan_ids'))
            if err:
                return None, err

        if club_id == CLUB_ID_DEFAULT:
            member.jiage = jiage
            member.guanshifc = guanshifc
            member.zuzhangfc = zuzhangfc
            member.save(update_fields=['jiage', 'guanshifc', 'zuzhangfc', 'UpdateTime'])

    return {'huiyuan_id': huiyuan_id}, None


MEMBER_CARD_FOLDER = 'beijing/huiyuan'


def upload_member_card_image(request, huiyuan_id, field, file_obj):
    """上传会员充值页卡片图，写入 club_huiyuan_price.card_image / card_bg。

    每次上传生成新相对路径并删除旧图，避免「同名覆盖但 CDN/浏览器仍显示旧图」。
    """
    import os
    import uuid
    from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss

    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG
    if field not in ('card_image', 'card_bg'):
        return None, '无效的图字段'
    ok, msg = validate_image(file_obj)
    if not ok:
        return None, msg

    club_id = club_id_for_write(request)
    row = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id).first()
    if not row:
        return None, '请先在当前俱乐部上架该会员'

    ext = os.path.splitext(getattr(file_obj, 'name', '') or '')[1].lower()
    if ext not in ('.png', '.jpg', '.jpeg', '.webp', '.gif'):
        ext = '.png'
    # 带时间戳+uuid，保证 URL 变化，前端/CDN 不会命中旧缓存
    stamp = uuid.uuid4().hex[:12]
    rel_path = f'{MEMBER_CARD_FOLDER}/{club_id}/{huiyuan_id}_{field}_{stamp}{ext}'

    if hasattr(file_obj, 'seek'):
        try:
            file_obj.seek(0)
        except Exception:
            pass
    if not upload_to_oss(file_obj, rel_path):
        return None, '上传 OSS 失败'

    old_path = (getattr(row, field, None) or '').strip()
    setattr(row, field, rel_path)
    row.save(update_fields=[field, 'UpdateTime'])

    if old_path and old_path != rel_path:
        try:
            delete_from_oss(old_path)
        except Exception as e:
            logger.warning('删除旧会员卡片图失败 club=%s hy=%s field=%s path=%s err=%s',
                           club_id, huiyuan_id, field, old_path, e)

    return {field: rel_path, 'huiyuan_id': huiyuan_id}, None


def enable_member_for_club(request, huiyuan_id, jiage, guanshifc, zuzhangfc, extra=None):
    """俱乐部上架集团会员类型。"""
    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG
    club_id = club_id_for_write(request)
    extra = extra or {}

    try:
        member = Huiyuan.query.get(huiyuan_id=huiyuan_id)
    except Huiyuan.DoesNotExist:
        return None, '集团会员类型不存在'

    if getattr(member, 'is_bundle', False):
        extra = dict(extra or {})
        extra['trial_enabled'] = False

    try:
        jiage = Decimal(str(jiage))
        guanshifc = Decimal(str(guanshifc))
        zuzhangfc = Decimal(str(zuzhangfc))
        if jiage <= 0:
            raise ValueError
        trial_fields = _parse_trial_fields(extra, jiage, guanshifc, zuzhangfc)
    except ValueError as e:
        return None, str(e) if str(e) else '金额或体验配置错误'

    row, created = ClubHuiyuanPrice.query.get_or_create(
        club_id=club_id,
        huiyuan_id=huiyuan_id,
        defaults={
            'jiage': jiage,
            'guanshifc': guanshifc,
            'zuzhangfc': zuzhangfc,
            'is_enabled': True,
            'bankuai_id': member.bankuai_id,
            **trial_fields,
        },
    )
    if not created:
        row.jiage = jiage
        row.guanshifc = guanshifc
        row.zuzhangfc = zuzhangfc
        row.is_enabled = True
        for k, v in trial_fields.items():
            setattr(row, k, v)
        row.save()
    if getattr(member, 'is_bundle', False) and extra.get('included_huiyuan_ids') is not None:
        from jituan.services.huiyuan_bundle import save_bundle_includes
        _, err = save_bundle_includes(club_id, huiyuan_id, extra.get('included_huiyuan_ids'))
        if err:
            return None, err
    return {'huiyuan_id': huiyuan_id}, None


def disable_member_for_club(request, huiyuan_id):
    """俱乐部软下架。"""
    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG
    club_id = club_id_for_write(request)
    updated = ClubHuiyuanPrice.objects.filter(
        club_id=club_id, huiyuan_id=huiyuan_id, is_enabled=True,
    ).update(is_enabled=False)
    if not updated:
        return None, '该会员在本俱乐部未上架或已下架'
    return {'huiyuan_id': huiyuan_id}, None


def add_global_member(jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None, is_bundle=False):
    """集团创建全局会员类型（仅集团后台）。"""

    def generate_huiyuan_id():
        while True:
            new_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
            if not Huiyuan.query.filter(huiyuan_id=new_id).exists():
                return new_id

    try:
        with transaction.atomic():
            new_id = generate_huiyuan_id()
            create_kwargs = dict(
                huiyuan_id=new_id,
                jieshao=jieshao,
                jiage=jiage,
                guanshifc=guanshifc,
                zuzhangfc=zuzhangfc,
                jtjieshao=jtjieshao or '',
                goumai_cishu=0,
                is_bundle=bool(is_bundle),
            )
            if bankuai_id is not None:
                create_kwargs['bankuai_id'] = bankuai_id
            member = Huiyuan.query.create(**create_kwargs)
    except IntegrityError as e:
        logger.exception('创建全局会员失败')
        return None, f'创建失败：{e}'
    except Exception as e:
        logger.exception('创建全局会员失败')
        return None, f'创建失败：{e}'

    return {'huiyuan_id': member.huiyuan_id}, None


def create_bundle_member_for_club(
    request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao,
    formal_days=30, included_huiyuan_ids=None,
):
    """俱乐部一键创建总会员：新建全局 is_bundle 类型 + 本俱乐部上架 + 配置包含子会员。"""
    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG

    included_huiyuan_ids = included_huiyuan_ids or []
    if not included_huiyuan_ids:
        return None, '总会员请至少选择一个包含的子会员'

    try:
        jiage = Decimal(str(jiage))
        guanshifc = Decimal(str(guanshifc))
        zuzhangfc = Decimal(str(zuzhangfc))
        if jiage <= 0:
            raise ValueError
        trial_fields = _parse_trial_fields(
            {'formal_days': formal_days, 'trial_enabled': False},
            jiage, guanshifc, zuzhangfc,
        )
    except ValueError as e:
        return None, str(e) if str(e) else '金额或天数配置错误'

    with transaction.atomic():
        data, err = add_global_member(
            jieshao, jiage, guanshifc, zuzhangfc, jtjieshao or '', is_bundle=True,
        )
        if err:
            return None, err

        extra = {
            **trial_fields,
            'included_huiyuan_ids': included_huiyuan_ids,
        }
        _, err = enable_member_for_club(
            request, data['huiyuan_id'], jiage, guanshifc, zuzhangfc, extra=extra,
        )
        if err:
            return None, err

    return data, None


# 兼容旧名：俱乐部不应再调用
def add_member_for_club(request, jieshao, jiage, guanshifc, zuzhangfc, jtjieshao, bankuai_id=None):
    if club_write_blocked(request):
        return None, CLUB_WRITE_SCOPE_MSG
    return None, '俱乐部不可新建会员类型，请从集团会员 catalog 上架'
