"""query 模块单测：排序 + 原子扣 + 异常路径 + FEAT-035 verified 字段。"""

from __future__ import annotations

from pathlib import Path

import pytest


@pytest.fixture(autouse=True)
def tmp_db(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
    db_path = tmp_path / "xq.db"
    monkeypatch.setenv("XIANGQIN_DB_PATH", str(db_path))
    from xiangqin import db as db_module

    db_module.DEFAULT_DB_PATH = db_path
    db_module.init_db(db_path)
    yield db_path


def _create_user(phone: str, **profile_fields) -> str:
    """工具：创建一个带 profile 的用户，返回 user_id。"""
    from xiangqin import auth, profile

    r = auth.register(phone)
    v = auth.verify(r.request_id, "123456")
    if profile_fields:
        profile.update(v.user_id, profile_fields)
    return v.user_id


def test_empty_where_rejected():
    from xiangqin import query

    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    with pytest.raises(query.QueryError) as e:
        query.run(caller, "")
    assert e.value.code == "dsl_empty"


def test_caller_excluded():
    from xiangqin import query

    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    _create_user("13900001111", gender="f", age=27, city="hangzhou")
    r = query.run(caller, "city=hangzhou")
    assert all(h.user_id != caller for h in r.results)
    assert r.total_hits == 1


def test_unset_profile_excluded():
    from xiangqin import query

    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    _create_user("13700001111")  # 没填 profile
    r = query.run(caller, "city=hangzhou")
    assert r.total_hits == 0


def test_age_filter():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    _create_user("13900001111", gender="f", age=22, city="hangzhou")
    _create_user("13900002222", gender="f", age=35, city="hangzhou")
    _create_user("13900003333", gender="f", age=28, city="hangzhou")

    r = query.run(caller, "city=hangzhou AND age>=25 AND age<=30")
    assert r.total_hits == 1
    assert r.results[0].age == 28


def test_tags_contains_filter():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    _create_user("13900001111", gender="f", age=27, city="hangzhou", tags=["登山", "做饭"])
    _create_user("13900002222", gender="f", age=29, city="hangzhou", tags=["瑜伽"])

    r = query.run(caller, "city=hangzhou AND tags CONTAINS 登山")
    assert r.total_hits == 1
    assert "登山" in r.results[0].tags


def test_no_pii_in_results():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    _create_user("13900001111", gender="f", age=27, city="hangzhou")

    r = query.run(caller, "city=hangzhou")
    for h in r.results:
        d = h.__dict__
        assert "phone" not in d
        assert "phone_hash" not in d
        assert "session_token" not in d


def test_limit_clamped():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    for i in range(5):
        _create_user(f"139{i:08d}", gender="f", age=25 + i, city="hangzhou")
    r = query.run(caller, "city=hangzhou", limit=3)
    assert r.total_hits == 3


def test_limit_validation():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    with pytest.raises(query.QueryError):
        query.run(caller, "city=hangzhou", limit=0)
    with pytest.raises(query.QueryError):
        query.run(caller, "city=hangzhou", limit=999)


def test_sql_injection_rejected():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    with pytest.raises(query.QueryError):
        query.run(caller, "gender=f; DROP TABLE users")


# ─── FEAT-035 verified 字段 ────────────────────────────


def test_query_result_has_verified_field():
    """结果集每条带 verified: bool。"""
    from xiangqin import query

    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    _create_user("13800002222", gender="f", age=27, city="hangzhou")
    r = query.run(caller, "gender=f")
    assert len(r.results) == 1
    assert r.results[0].verified is False  # 默认未实名


def test_query_verified_true_after_realname_verify(monkeypatch):
    """实名后结果里 verified=True。"""
    from xiangqin import query, realname

    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    target = _create_user("13800002222", gender="f", age=27, city="hangzhou")

    realname.verify(target, "张三", "110101199001011237")
    r = query.run(caller, "gender=f")
    assert r.results[0].verified is True


def test_dsl_verified_filter_true(monkeypatch):
    """用户写 `AND verified=true` 只查已实名。"""
    from xiangqin import query, realname

    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237,220203198502020012")
    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    a = _create_user("13800002222", gender="f", age=27, city="hangzhou")
    _create_user("13800003333", gender="f", age=26, city="hangzhou")  # 未实名

    realname.verify(a, "张三", "110101199001011237")
    r = query.run(caller, "gender=f AND verified=true")
    ids = [h.user_id for h in r.results]
    assert a in ids  # 实名 A 在
    assert len(r.results) == 1  # 未实名不在


def test_dsl_verified_filter_false(monkeypatch):
    """`AND verified=false` 只查未实名。"""
    from xiangqin import query, realname

    monkeypatch.setenv("XIANGQIN_TEST_CERT_IDS", "110101199001011237")
    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    a = _create_user("13800002222", gender="f", age=27, city="hangzhou")
    b = _create_user("13800003333", gender="f", age=26, city="hangzhou")

    realname.verify(a, "张三", "110101199001011237")
    r = query.run(caller, "gender=f AND verified=false")
    ids = [h.user_id for h in r.results]
    assert b in ids
    assert a not in ids


def test_dsl_verified_bad_value_rejected():
    """verified 值只接受 true/false。"""
    from xiangqin import query

    caller = _create_user("13800001111", gender="m", age=28, city="hangzhou")
    with pytest.raises(query.QueryError) as e:
        query.run(caller, "gender=f AND verified=yes")
    assert e.value.code == "dsl_bad_value"


# ============ FEAT-019 rate limit ============


def test_query_rate_limit_min_window(monkeypatch):
    """第 31 次 query 在 60s 内触发 query_min 限流。"""
    from xiangqin import query, rate_limit

    caller = _create_user("13800009001", gender="m", age=28, city="hangzhou")
    monkeypatch.setattr(rate_limit, "now_ts", lambda: 1_000_000)

    for _ in range(30):
        query.run(caller, "gender=f")

    with pytest.raises(query.QueryError) as ei:
        query.run(caller, "gender=f")
    assert ei.value.code == "rate_limited"


def test_query_rate_limit_endpoint_returns_429():
    """HTTP 层 /query 超限映射到 429。"""
    from fastapi.testclient import TestClient

    from xiangqin.app import app

    client = TestClient(app)
    r = client.post("/auth/register", json={"phone": "13800009101"})
    req = r.json()["request_id"]
    v = client.post("/auth/verify", json={"request_id": req, "code": "123456"})
    tok = v.json()["session_token"]
    hdr = {"Authorization": f"Bearer {tok}"}

    for _ in range(30):
        r = client.post("/query", headers=hdr, json={"where": "gender=f", "limit": 10})
        assert r.status_code == 200

    r = client.post("/query", headers=hdr, json={"where": "gender=f", "limit": 10})
    assert r.status_code == 429
    assert r.json()["detail"]["code"] == "rate_limited"
