"""用户所属俱乐部解析。"""
import logging

from django.db import IntegrityError, transaction

from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import UserWxOpenid

logger = logging.getLogger(__name__)


def resolve_invite_party_club(user, request_club_id=None):
    """
    邀请链校验用：只返回已落库的俱乐部归属。
    未归属任何俱乐部时返回 None（勿默认 xq，避免星之界扫码误拦）。
    """
    if not user:
        return None

    uid = getattr(user, 'UserUID', None) or getattr(user, 'yonghuid', None)
    req = (request_club_id or '').strip()

    # 当前小程序 openid 绑定优先（ClubID 字段曾默认 xq，会误判新用户）
    if req and uid:
        if UserWxOpenid.query.filter(yonghuid=uid, club_id=req).exists():
            return req

    explicit = (getattr(user, 'ClubID', None) or '').strip()
    if explicit:
        if explicit == CLUB_ID_DEFAULT and uid:
            has_real_xq = (
                UserWxOpenid.query.filter(yonghuid=uid, club_id=CLUB_ID_DEFAULT).exists()
                or bool(getattr(user, 'OpenID', None))
            )
            if not has_real_xq:
                explicit = ''
        if explicit:
            return explicit

    if uid:
        binding = UserWxOpenid.query.filter(yonghuid=uid).order_by('id').first()
        if binding:
            return binding.club_id

    return None


def _user_querysets():
    """兼容 gvsdsdk User.query 与 Django User.objects。"""
    from users.business_models import User

    seen = set()
    for qs in (getattr(User, 'objects', None), getattr(User, 'query', None)):
        if qs is None or id(qs) in seen:
            continue
        seen.add(id(qs))
        yield qs


def find_user_by_wx_openid(club_id, openid):
    """按俱乐部 openid 绑定或 legacy UserName/OpenID 定位用户。"""
    from users.business_models import User

    openid = (openid or '').strip()
    club_id = (club_id or '').strip()
    if not openid:
        return None

    def _user_by_uid(uid):
        if not uid:
            return None
        user = User.objects.filter(UserUID=uid).first()
        if not user:
            user = User.query.filter(UserUID=uid).first()
        return user

    # 已有 openid 绑定（任意俱乐部）优先，避免重复注册
    binding_any = UserWxOpenid.query.filter(openid=openid).order_by('id').first()
    if binding_any:
        user = _user_by_uid(binding_any.yonghuid)
        if user:
            return user

    if club_id:
        binding = UserWxOpenid.query.filter(club_id=club_id, openid=openid).first()
        if binding:
            user = _user_by_uid(binding.yonghuid)
            if user:
                return user

    # 星之界等用户常 UserName=openid 且 OpenID 为空
    for manager in _user_querysets():
        user = manager.filter(UserName=openid).first()
        if user:
            return user
        user = manager.filter(OpenID=openid).first()
        if user:
            return user
    return None


def ensure_wx_openid_binding(club_id, openid, user, unionid='', wx_appid=''):
    """
    绑定 openid。
    wx_appid 用于区分小程序 / 服务号；只清理「同 club + 同 wx_appid」的旧绑定，
    避免 H5 登录冲掉小程序 openid（或反过来）。
    """
    if not club_id or not openid or not user:
        return None
    uid = getattr(user, 'UserUID', None)
    if not uid:
        return None

    club_id = (club_id or '').strip()
    openid = (openid or '').strip()
    wx_appid = (wx_appid or '').strip()

    binding = UserWxOpenid.query.filter(club_id=club_id, openid=openid).first()
    if binding and binding.yonghuid == uid:
        if wx_appid and getattr(binding, 'wx_appid', '') != wx_appid:
            binding.wx_appid = wx_appid
            if unionid:
                binding.unionid = unionid
            binding.save()
        return binding

    try:
        with transaction.atomic():
            # 仅清理同一 AppID 下的过期绑定；小程序与服务号可并存
            stale_qs = UserWxOpenid.query.filter(yonghuid=uid, club_id=club_id).exclude(openid=openid)
            if wx_appid:
                stale_qs = stale_qs.filter(wx_appid=wx_appid)
            else:
                stale_qs = stale_qs.filter(wx_appid='')
            stale_count = stale_qs.count()
            if stale_count:
                logger.info(
                    '[WX_OPENID_BIND] uid=%s club=%s appid=%s 清除 %d 条过期 openid 绑定',
                    uid, club_id, wx_appid or '(legacy)', stale_count,
                )
                stale_qs.delete()

            binding = UserWxOpenid.query.filter(club_id=club_id, openid=openid).first()
            if binding:
                if binding.yonghuid != uid:
                    return binding
                if wx_appid and getattr(binding, 'wx_appid', '') != wx_appid:
                    binding.wx_appid = wx_appid
                    binding.save(update_fields=['wx_appid'])
                return binding

            row = UserWxOpenid.query.create(
                club_id=club_id,
                openid=openid,
                yonghuid=uid,
                unionid=unionid or getattr(user, 'UnionID', None) or '',
                wx_appid=wx_appid,
            )
            if club_id == CLUB_ID_DEFAULT and getattr(user, 'OpenID', None) != openid:
                user.OpenID = openid
                user.save(update_fields=['OpenID'])
            return row
    except IntegrityError:
        return UserWxOpenid.query.filter(club_id=club_id, openid=openid).first()


def resolve_or_create_wx_user(club_id, openid, generate_uid, unionid='', wx_appid=''):
    """
    微信登录/注册统一找用户，避免 UserName(openid) 唯一键冲突。
    星之界等俱乐部用户常 UserName=openid 且 OpenID 为空，不可 get_or_create(OpenID=...)。
    返回 (user, created)。
    """
    import uuid
    from users.business_models import User

    openid = (openid or '').strip()
    club_id = (club_id or '').strip()
    wx_appid = (wx_appid or '').strip()
    if not openid:
        raise ValueError('openid 不能为空')

    user = find_user_by_wx_openid(club_id, openid)
    if user:
        ensure_wx_openid_binding(club_id, openid, user, unionid, wx_appid=wx_appid)
        return user, False

    # 有 unionid 时尽量合并到已有用户（小程序 / 服务号同人）
    if unionid:
        binding_u = UserWxOpenid.query.filter(unionid=unionid).order_by('id').first()
        if binding_u:
            user = User.objects.filter(UserUID=binding_u.yonghuid).first()
            if not user:
                user = User.query.filter(UserUID=binding_u.yonghuid).first()
            if user:
                ensure_wx_openid_binding(club_id, openid, user, unionid, wx_appid=wx_appid)
                return user, False
        for manager in _user_querysets():
            user = manager.filter(UnionID=unionid).first()
            if user:
                ensure_wx_openid_binding(club_id, openid, user, unionid, wx_appid=wx_appid)
                return user, False

    try:
        with transaction.atomic():
            user = User(
                UserUUID=uuid.uuid4().bytes,
                UserUID=generate_uid(),
                UserName=openid,
                OpenID=openid if club_id == CLUB_ID_DEFAULT else None,
                UnionID=unionid or None,
                ClubID='',
            )
            user.save()
    except IntegrityError:
        user = find_user_by_wx_openid(club_id, openid)
        if not user:
            raise
        ensure_wx_openid_binding(club_id, openid, user, unionid, wx_appid=wx_appid)
        return user, False

    ensure_wx_openid_binding(club_id, openid, user, unionid, wx_appid=wx_appid)
    return user, True


def get_user_club_id(user):
    """
    解析用户归属 club（注册/邀请链隔离用）。
    优先级：User.ClubID → user_wx_openid → 默认 xq。
    """
    if not user:
        return CLUB_ID_DEFAULT

    club_id = getattr(user, 'ClubID', None) or getattr(user, 'club_id', None)
    if club_id:
        return club_id

    uid = getattr(user, 'UserUID', None) or getattr(user, 'yonghuid', None)
    if uid:
        binding = UserWxOpenid.query.filter(yonghuid=uid).order_by('id').first()
        if binding:
            return binding.club_id

    return CLUB_ID_DEFAULT


def ensure_user_club_id(user, club_id):
    """登录/注册后确保 User.ClubID 与绑定 club 一致。"""
    if not user or not club_id:
        return
    current = getattr(user, 'ClubID', None)
    if current != club_id and hasattr(user, 'ClubID'):
        user.ClubID = club_id
        user.save(update_fields=['ClubID'])


def _looks_like_wx_openid(value):
    """微信 openid 常见形态：以 o 开头、长度约 28。"""
    v = (value or '').strip()
    return len(v) >= 20 and v.startswith('o')


def _client_type_from_request(request):
    if not request:
        return 'mini'
    meta = getattr(request, 'META', None) or {}
    header = (meta.get('HTTP_X_CLIENT') or meta.get('HTTP_X_PAY_CLIENT') or '').strip().lower()
    data = getattr(request, 'data', None) or {}
    body = ''
    if isinstance(data, dict):
        body = (data.get('client_type') or data.get('pay_client') or '').strip().lower()
    val = header or body
    if val in ('h5', 'official', 'mp-h5', 'oa'):
        return 'h5'
    return 'mini'


def resolve_prefer_wx_appid(club_id, client_type='mini'):
    """按端解析应付的 AppID：H5→服务号；小程序→wx_appid。"""
    from jituan.models import Club
    cid = (club_id or '').strip()
    if not cid:
        return ''
    try:
        club = Club.query.get(club_id=cid)
    except Exception:
        return ''
    if client_type == 'h5':
        return (club.official_appid or '').strip()
    return (club.wx_appid or club.pay_app_id or '').strip()


def resolve_club_payment_openid(user, club_id=None, prefer_wx_appid=''):
    """
    支付专用：按俱乐部取 openid。
    prefer_wx_appid 有值时优先取该 AppID 绑定（H5 服务号 / 小程序分流）。
    1) user_wx_openid(yonghuid, club_id[, wx_appid])
    2) 无本 club 绑定但有其他 club → 拒绝（须在本端重新登录）
    3) 无任何绑定时 lazy backfill：xq→User.OpenID；xzj 等→UserName(openid)
    """
    if not user:
        return '', 'none'
    cid = (club_id or '').strip() or get_user_club_id(user)
    uid = getattr(user, 'UserUID', None) or getattr(user, 'yonghuid', None)
    if not uid or not cid:
        return '', 'none'

    prefer = (prefer_wx_appid or '').strip()
    qs = UserWxOpenid.query.filter(yonghuid=uid, club_id=cid)
    binding = None
    if prefer:
        binding = qs.filter(wx_appid=prefer).order_by('-id').first()
        if not binding and prefer:
            # 历史行 wx_appid 为空：仅小程序端可回落
            pass
    if not binding and not prefer:
        binding = qs.order_by('-id').first()
    if not binding and prefer:
        # 小程序端：允许回落空 wx_appid 历史绑定
        from jituan.models import Club
        try:
            club = Club.query.get(club_id=cid)
            mini = (club.wx_appid or club.pay_app_id or '').strip()
        except Exception:
            mini = ''
        if prefer == mini:
            binding = qs.filter(wx_appid='').order_by('-id').first()

    if binding and binding.openid:
        return binding.openid.strip(), f'user_wx_openid#{binding.id}'

    if prefer:
        logger.warning(
            '[PAY_OPENID] uid=%s club=%s 无 appid=%s 绑定，须先在对应端完成微信登录',
            uid, cid, prefer,
        )
        return '', 'missing_appid_bind'

    other_clubs = list(
        UserWxOpenid.query.filter(yonghuid=uid)
        .exclude(club_id=cid)
        .values_list('club_id', flat=True)[:5]
    )
    if other_clubs:
        logger.warning(
            '[PAY_OPENID] uid=%s 无 club=%s 绑定，已有其他俱乐部 %s；须在本小程序重新登录',
            uid, cid, other_clubs,
        )
        return '', 'other_club_only'

    username = (getattr(user, 'UserName', None) or '').strip()
    legacy_oid = (getattr(user, 'OpenID', None) or getattr(user, 'openid', None) or '').strip()
    candidate = ''
    source = ''
    if cid == CLUB_ID_DEFAULT:
        if legacy_oid and _looks_like_wx_openid(legacy_oid):
            candidate, source = legacy_oid, 'legacy_OpenID'
    elif _looks_like_wx_openid(username):
        candidate, source = username, 'legacy_UserName'

    if candidate:
        logger.info('[PAY_OPENID] uid=%s club=%s lazy backfill from %s', uid, cid, source)
        ensure_wx_openid_binding(cid, candidate, user)
        return candidate, source

    logger.warning(
        '[PAY_OPENID] uid=%s club=%s 无绑定 legacy_UserName=%s… legacy_OpenID=%s',
        uid, cid, username[:10] if username else '', bool(legacy_oid),
    )
    return '', 'missing'


def get_wx_openid_for_user(user, club_id=None, prefer_wx_appid=''):
    openid, _ = resolve_club_payment_openid(user, club_id, prefer_wx_appid=prefer_wx_appid)
    return openid


def get_wx_openid_for_user_with_source(user, club_id=None, prefer_wx_appid=''):
    return resolve_club_payment_openid(user, club_id, prefer_wx_appid=prefer_wx_appid)


def get_payment_openid(request, user=None, club_id=None):
    """下单/充值：按请求俱乐部 + 端类型解析 openid。"""
    from jituan.services.club_write import resolve_club_id_for_write
    user = user or getattr(request, 'user', None)
    cid = (club_id or '').strip() or resolve_club_id_for_write(request, user)
    client_type = _client_type_from_request(request)
    prefer = resolve_prefer_wx_appid(cid, client_type)
    openid, source = get_wx_openid_for_user_with_source(user, cid, prefer_wx_appid=prefer)
    if openid:
        logger.info(
            '[PAY_OPENID] uid=%s club=%s client=%s appid=%s openid=%s… source=%s',
            getattr(user, 'UserUID', ''), cid, client_type, prefer or '-',
            openid[:6] + '...' if len(openid) > 10 else openid, source,
        )
    elif user:
        uid = getattr(user, 'UserUID', None) or getattr(user, 'yonghuid', None)
        logger.warning(
            '[PAY_OPENID] 无法解析支付 openid uid=%s club=%s client=%s appid=%s source=%s user_club=%s',
            uid, cid, client_type, prefer or '-', source, get_user_club_id(user),
        )
    return openid, cid


def get_pay_app_source(request):
    """供统一下单选用 AppID：h5→official，其它→mini。"""
    return 'official' if _client_type_from_request(request) == 'h5' else 'mini'
