"""query 模块单测：排序 + 原子扣 + 异常路径。"""

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 _set_exposure(user_id: str, remaining: int) -> None:
    from xiangqin import db as db_module

    with db_module.get_conn() as conn:
        conn.execute(
            "UPDATE exposure_usage SET remaining = ?, total_purchased = ? WHERE user_id = ?",
            (remaining, remaining, 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_paid_user_first():
    from xiangqin import query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    free = _create_user("13900001111", gender="f", age=27, city="hangzhou")
    paid = _create_user("13900002222", gender="f", age=29, city="hangzhou")
    _set_exposure(paid, 10)

    r = query.run(caller, "city=hangzhou AND gender=f")
    assert r.total_hits == 2
    assert r.results[0].user_id == paid
    assert r.results[0].is_promoted is True
    assert r.results[1].user_id == free
    assert r.results[1].is_promoted is False


def test_exposure_decremented():
    from xiangqin import db, query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    paid = _create_user("13900002222", gender="f", age=29, city="hangzhou")
    _set_exposure(paid, 3)

    r1 = query.run(caller, "city=hangzhou AND gender=f")
    assert r1.exposure_consumed == 1

    with db.get_conn() as conn:
        remaining = conn.execute(
            "SELECT remaining FROM exposure_usage WHERE user_id = ?", (paid,)
        ).fetchone()["remaining"]
    assert remaining == 2


def test_exposure_drains_to_free_pool():
    from xiangqin import db, query

    caller = _create_user("13000001111", gender="m", age=28, city="hangzhou")
    paid = _create_user("13900002222", gender="f", age=29, city="hangzhou")
    _set_exposure(paid, 1)

    # 第一次 query：扣到 0
    r1 = query.run(caller, "city=hangzhou AND gender=f")
    assert r1.results[0].is_promoted is True
    assert r1.exposure_consumed == 1

    # 第二次 query：已经回到普通池
    r2 = query.run(caller, "city=hangzhou AND gender=f")
    assert r2.results[0].is_promoted is False
    assert r2.exposure_consumed == 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")
