"""realname 模块单测（FEAT-031 PRD-001 阶段 1）。

不真调阿里云 API：
- 白名单测试号：走 is_test_cert_id 分支 → mock passed
- 其他号：monkeypatch _call_aliyun 返回伪造的 BizCode 响应
"""

from __future__ import annotations

import importlib

import pytest

from xiangqin import auth, db, realname


@pytest.fixture(autouse=True)
def _restore_realname(monkeypatch):
    """覆盖 conftest 的 _bypass_realname_by_default，让本文件测真实 assert_user_verified 行为。"""
    realname_mod = importlib.reload(realname)
    monkeypatch.setattr(realname, "assert_user_verified", realname_mod.assert_user_verified)
    monkeypatch.setattr(realname, "is_verified", realname_mod.is_verified)


# ─── fixtures ─────────────────────────────────────────


@pytest.fixture
def seed_user():
    """建一个 user 返回 user_id（复用 auth.register/verify 路径）。"""
    r = auth.register("13800001111")
    v = auth.verify(r.request_id, "123456")
    return v.user_id


# ─── 输入校验 ─────────────────────────────────────────


def test_empty_name_rejected(seed_user):
    with pytest.raises(realname.RealnameError) as e:
        realname.verify(seed_user, "", "110101199001011237")
    assert e.value.code == "invalid_name"


def test_bad_cert_id_format_rejected(seed_user):
    with pytest.raises(realname.RealnameError) as e:
        realname.verify(seed_user, "张三", "123")
    assert e.value.code == "invalid_cert_id"


def test_cert_id_with_lowercase_x_accepted(seed_user, monkeypatch):
    """末位 'X' 或 'x' 都是合法（18 位末位可能是校验位 X）。"""
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "11010119900101123x")
    result = realname.verify(seed_user, "张三", "11010119900101123x")
    assert result.verified is True


# ─── 白名单 mock ──────────────────────────────────────


def test_whitelist_cert_id_mocks_passed(seed_user, monkeypatch):
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    # 伪装 _call_aliyun 炸（白名单应不调它）
    monkeypatch.setattr(realname, "_call_aliyun", lambda *a: pytest.fail("不该调 aliyun API"))

    result = realname.verify(seed_user, "测试", "110101199001011237")
    assert result.verified is True
    assert result.status == "passed"
    assert result.biz_code is None  # mock 路径无 biz_code


def test_whitelist_multiple_cert_ids(monkeypatch):
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237,220203198502020012")
    assert realname.is_test_cert_id("110101199001011237")
    assert realname.is_test_cert_id("220203198502020012")
    assert not realname.is_test_cert_id("330304197803030001")


def test_empty_test_env_means_no_whitelist(monkeypatch):
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    assert not realname.is_test_cert_id("110101199001011237")


# ─── BizCode 翻译 ────────────────────────────────────


@pytest.mark.parametrize(
    "biz_code,expected_status,expect_verified",
    [
        ("1", "passed", True),
        ("2", "mismatch", False),
        ("3", "unknown_id", False),
        ("5", "name_wrong", False),
        ("99", "error", False),  # 未知码
    ],
)
def test_translate_biz_code(seed_user, monkeypatch, biz_code, expected_status, expect_verified):
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": biz_code}},
    )
    result = realname.verify(seed_user, "张三", "110101199001011237")
    assert result.status == expected_status
    assert result.verified == expect_verified
    assert result.biz_code == biz_code


# ─── 持久化 ───────────────────────────────────────────


def test_passed_persists_to_db(seed_user, monkeypatch):
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    result = realname.verify(seed_user, "张三", "110101199001011237")
    assert result.verified

    with db.get_conn() as conn:
        row = conn.execute(
            "SELECT verified_at, cert_id_hash, legal_name FROM users WHERE id = ?",
            (seed_user,),
        ).fetchone()
    assert row["verified_at"] is not None
    assert row["legal_name"] == "张三"
    assert row["cert_id_hash"] == db.hash_cert_id("110101199001011237")


def test_failed_does_not_persist(seed_user, monkeypatch):
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": "2"}},
    )
    result = realname.verify(seed_user, "张三", "110101199001011237")
    assert not result.verified

    with db.get_conn() as conn:
        row = conn.execute("SELECT verified_at FROM users WHERE id = ?", (seed_user,)).fetchone()
    assert row["verified_at"] is None


def test_cert_id_bound_to_another_user_raises(monkeypatch):
    """同一身份证号已被 user A 绑，user B 绑同号 → RealnameError cert_id_bound。"""
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    # 两个用户
    r1 = auth.register("13800001111")
    u1 = auth.verify(r1.request_id, "123456").user_id
    r2 = auth.register("13800002222")
    u2 = auth.verify(r2.request_id, "123456").user_id

    # u1 实名成功
    realname.verify(u1, "张三", "110101199001011237")

    # u2 用同号 → 应抛 cert_id_bound
    with pytest.raises(realname.RealnameError) as e:
        realname.verify(u2, "张三", "110101199001011237")
    assert e.value.code == "cert_id_bound"
    assert e.value.http_status == 409


# ─── 闸门 assert_user_verified ───────────────────────


def test_assert_user_verified_unverified_blocks(seed_user):
    with pytest.raises(realname.RealnameError) as e:
        realname.assert_user_verified(seed_user)
    assert e.value.code == "not_verified"
    assert e.value.http_status == 403


def test_assert_user_verified_passed_allows(seed_user, monkeypatch):
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    realname.verify(seed_user, "张三", "110101199001011237")
    # 不抛 = 通过
    realname.assert_user_verified(seed_user)


def test_assert_user_verified_missing_user(seed_user):
    with pytest.raises(realname.RealnameError) as e:
        realname.assert_user_verified("no-such-user")
    assert e.value.code == "user_not_found"


# ─── is_verified 标签（DSL / 结果响应用） ────────────


def test_is_verified_false_by_default(seed_user):
    assert realname.is_verified(seed_user) is False


def test_is_verified_true_after_passed(seed_user, monkeypatch):
    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    realname.verify(seed_user, "张三", "110101199001011237")
    assert realname.is_verified(seed_user) is True


# ─── API 错误分支 ─────────────────────────────────────


def test_api_non_200_code_raises(seed_user, monkeypatch):
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "401", "Message": "参数非法"},
    )
    with pytest.raises(realname.RealnameError) as e:
        realname.verify(seed_user, "张三", "110101199001011237")
    assert e.value.code == "aliyun_api_error"
    assert e.value.http_status == 503


# ─── 脱敏 ────────────────────────────────────────────


def test_rate_limit_after_3_failures(seed_user, monkeypatch):
    """FEAT-035：24h 内 3 次失败后锁定，第 4 次抛 too_many_attempts 429。"""
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": "2"}},
    )
    # 连 3 次失败（BizCode=2）
    for _ in range(3):
        result = realname.verify(seed_user, "张三", "110101199001011237")
        assert not result.verified
    # 第 4 次：限流拦截
    with pytest.raises(realname.RealnameError) as e:
        realname.verify(seed_user, "张三", "110101199001011237")
    assert e.value.code == "too_many_attempts"
    assert e.value.http_status == 429


def test_rate_limit_reset_on_success(seed_user, monkeypatch):
    """成功后计数清零。"""
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    # 先 2 次失败
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": "2"}},
    )
    realname.verify(seed_user, "张三", "110101199001011237")
    realname.verify(seed_user, "张三", "110101199001011237")
    with db.get_conn() as conn:
        row = conn.execute(
            "SELECT realname_failed_count FROM users WHERE id = ?", (seed_user,)
        ).fetchone()
    assert row["realname_failed_count"] == 2

    # 3 次成功（切 BizCode=1）
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": "1"}},
    )
    result = realname.verify(seed_user, "张三", "110101199001011237")
    assert result.verified
    with db.get_conn() as conn:
        row = conn.execute(
            "SELECT realname_failed_count, realname_last_failed_at FROM users WHERE id = ?",
            (seed_user,),
        ).fetchone()
    assert row["realname_failed_count"] == 0
    assert row["realname_last_failed_at"] is None


def test_rate_limit_window_expires(seed_user, monkeypatch):
    """失败 >3 次但 >24h 前的失败 → 可以重试（窗口过期）。"""
    monkeypatch.delenv("XIANGQIN_TEST_CERT_IDS", raising=False)
    # 手动写 DB：3 次失败 + last_failed_at 是 25h 前
    with db.get_conn() as conn:
        conn.execute(
            "UPDATE users SET realname_failed_count = 3, realname_last_failed_at = ? WHERE id = ?",
            (db.now_ts() - 25 * 3600, seed_user),
        )
    # BizCode=1 成功路径
    monkeypatch.setattr(
        realname,
        "_call_aliyun",
        lambda *a: {"Code": "200", "ResultObject": {"BizCode": "1"}},
    )
    result = realname.verify(seed_user, "张三", "110101199001011237")
    assert result.verified  # 窗口过期，允许重试


def test_mask_hides_middle():
    assert realname._mask("110101199001011237") == "1101**********1237"
    assert realname._mask("123") == "****"
