"""#1719：已登录用户微信绑定/解绑 Use Case 测试。

覆盖：
- bind：幂等重复绑定、未绑定成功、当前账号已绑其他微信、openid/unionid 冲突 409、用户不存在
- unbind：成功清 openid+unionid、未绑定拒绝、无其他登录方式拒绝、密码/手机/真实邮箱各兜底放行、用户不存在
"""

from __future__ import annotations

from types import SimpleNamespace

import pytest

from packages.application.auth.wechat_bind_use_case import (
    WechatBindRequest,
    WechatBindUseCase,
    WechatUnbindUseCase,
)


def _user(
    user_id="u-1",
    wechat_openid=None,
    wechat_unionid=None,
    password_hash="hashed-pw",
    phone=None,
    phone_verified=False,
    email="user@example.com",
    email_verified=True,
):
    return SimpleNamespace(
        id=user_id,
        wechat_openid=wechat_openid,
        wechat_unionid=wechat_unionid,
        password_hash=password_hash,
        phone=phone,
        phone_verified=phone_verified,
        email=email,
        email_verified=email_verified,
    )


class _FakeRepo:
    """内存仓储：按 id/openid/unionid 建索引，save 原地更新。"""

    def __init__(self, users):
        self.users = {u.id: u for u in users}
        self.saved = []

    def find_by_id(self, user_id):
        return self.users.get(user_id)

    def find_by_wechat_openid(self, openid):
        for u in self.users.values():
            if u.wechat_openid == openid:
                return u
        return None

    def find_by_wechat_unionid(self, unionid):
        if not unionid:
            return None
        for u in self.users.values():
            if u.wechat_unionid == unionid:
                return u
        return None

    def save(self, user):
        self.saved.append(user)


# ==================== bind ====================


def test_bind_success_when_not_bound():
    user = _user()
    repo = _FakeRepo([user])
    result, err, status = WechatBindUseCase(repo).bind(
        WechatBindRequest(user_id="u-1", openid="wx-openid-1", unionid="wx-union-1")
    )
    assert err is None
    assert status == 200
    assert result.user.wechat_openid == "wx-openid-1"
    assert result.user.wechat_unionid == "wx-union-1"
    assert repo.saved == [user]


def test_bind_idempotent_same_openid():
    user = _user(wechat_openid="wx-openid-1", wechat_unionid="wx-union-1")
    repo = _FakeRepo([user])
    result, err, status = WechatBindUseCase(repo).bind(
        WechatBindRequest(user_id="u-1", openid="wx-openid-1", unionid="wx-union-1")
    )
    assert err is None
    assert status == 200
    assert result.user is user
    assert repo.saved == []  # 幂等不写库


def test_bind_conflict_user_already_bound_other_wechat():
    user = _user(wechat_openid="wx-old")
    repo = _FakeRepo([user])
    result, err, status = WechatBindUseCase(repo).bind(WechatBindRequest(user_id="u-1", openid="wx-new"))
    assert result is None
    assert status == 409
    assert "已绑定微信" in err


def test_bind_conflict_openid_used_by_other_user():
    user = _user(user_id="u-1")
    other = _user(user_id="u-2", wechat_openid="wx-openid-1")
    repo = _FakeRepo([user, other])
    result, err, status = WechatBindUseCase(repo).bind(WechatBindRequest(user_id="u-1", openid="wx-openid-1"))
    assert result is None
    assert status == 409
    assert "已绑定其他账号" in err
    assert user.wechat_openid is None  # 未写库


def test_bind_conflict_unionid_used_by_other_user():
    user = _user(user_id="u-1")
    # openid 不同，但 unionid 指向同一微信主体
    other = _user(user_id="u-2", wechat_openid="wx-other", wechat_unionid="wx-union-x")
    repo = _FakeRepo([user, other])
    result, err, status = WechatBindUseCase(repo).bind(
        WechatBindRequest(user_id="u-1", openid="wx-openid-1", unionid="wx-union-x")
    )
    assert result is None
    assert status == 409
    assert "微信主体" in err


def test_bind_missing_openid_returns_400():
    repo = _FakeRepo([_user()])
    result, err, status = WechatBindUseCase(repo).bind(WechatBindRequest(user_id="u-1", openid=""))
    assert result is None
    assert status == 400
    assert "openid" in err


def test_bind_user_not_found_returns_404():
    repo = _FakeRepo([])
    result, err, status = WechatBindUseCase(repo).bind(WechatBindRequest(user_id="ghost", openid="wx-openid-1"))
    assert result is None
    assert status == 404


def test_bind_fills_unionid_when_existing_user_has_none():
    # 用户历史上只绑了 openid（unionid 为空），再次绑定时补齐 unionid 不冲突
    user = _user(wechat_openid="wx-openid-1", wechat_unionid=None)
    repo = _FakeRepo([user])
    result, err, status = WechatBindUseCase(repo).bind(
        WechatBindRequest(user_id="u-1", openid="wx-openid-1", unionid="wx-union-new")
    )
    # openid 相同 → 幂等成功（不覆盖 unionid，保持数据稳定）
    assert err is None
    assert status == 200


# ==================== unbind ====================


def test_unbind_success_with_real_verified_email():
    # 默认 _user 即 real@example.com 且 email_verified=True
    user = _user(wechat_openid="wx-openid-1", wechat_unionid="wx-union-1")
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert err is None
    assert status == 200
    assert result.user.wechat_openid is None
    assert result.user.wechat_unionid is None
    assert repo.saved == [user]


def test_unbind_rejected_when_only_random_password_hash():
    # 微信注册用户：随机密码 hash 存在、邮箱是 @wechat.local 占位、无手机 → 不允许解绑
    user = _user(
        wechat_openid="wx-openid-1",
        password_hash="random-secret-hash",
        email="abc@wechat.local",
        email_verified=True,
    )
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert result is None
    assert status == 400
    assert "登录方式" in err
    assert user.wechat_openid == "wx-openid-1"  # 未写库


def test_unbind_allowed_with_verified_phone_even_without_password():
    user = _user(
        wechat_openid="wx-openid-1",
        password_hash="",
        phone="13800000000",
        phone_verified=True,
        email="wx@wechat.local",
        email_verified=True,
    )
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert err is None
    assert status == 200
    assert result.user.wechat_openid is None


def test_unbind_rejected_when_no_other_login_method():
    # 无手机、邮箱占位 → 唯一登录方式就是微信，禁止解绑
    user = _user(
        wechat_openid="wx-openid-1",
        password_hash="",
        email="abc@wechat.local",
        email_verified=True,
    )
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert result is None
    assert status == 400
    assert "登录方式" in err
    assert user.wechat_openid == "wx-openid-1"  # 未写库


def test_unbind_not_bound_returns_400():
    user = _user()  # 未绑定
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert result is None
    assert status == 400
    assert "未绑定" in err


def test_unbind_user_not_found_returns_404():
    repo = _FakeRepo([])
    result, err, status = WechatUnbindUseCase(repo).unbind("ghost")
    assert result is None
    assert status == 404


def test_unbind_unverified_phone_does_not_count():
    # 手机未验证不算有效登录方式
    user = _user(
        wechat_openid="wx-openid-1",
        password_hash="",
        phone="13800000000",
        phone_verified=False,
        email="abc@wechat.local",
        email_verified=True,
    )
    repo = _FakeRepo([user])
    result, err, status = WechatUnbindUseCase(repo).unbind("u-1")
    assert result is None
    assert status == 400
