"""CLI 烟雾测试。只测：
- --help / --version 打印
- 未登录早退路径（不触 HTTP）
- client_session save/load/clear

HTTP 集成走 test_app.py 的 TestClient 和本文件 ASGI runner，不重复。
"""

from __future__ import annotations

import pytest
from click.testing import CliRunner


@pytest.fixture(autouse=True)
def isolate_session(tmp_path, monkeypatch):
    """把 ~/.xiangqin 指到 tmp_path，防污染用户机。"""
    monkeypatch.setenv("XIANGQIN_CONFIG_DIR", str(tmp_path / ".xiangqin"))
    from xiangqin import client_session

    client_session.SESSION_DIR = tmp_path / ".xiangqin"
    client_session.SESSION_FILE = client_session.SESSION_DIR / "session.json"
    yield


@pytest.fixture
def runner():
    return CliRunner()


# ---------- help / version ----------


def test_help(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["--help"])
    assert r.exit_code == 0
    assert "相亲" in r.output or "xiangqin" in r.output


def test_version(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["--version"])
    assert r.exit_code == 0


@pytest.mark.parametrize(
    "sub",
    [
        "health",
        "register",
        "verify",
        "logout",
        "me",
        "profile",
        "query",
        "dm",
        "inbox",
        "envelope",
        "block",
        "unblock",
        "blocklist",
    ],
)
def test_subcommand_help(runner, sub):
    from xiangqin.cli import cli

    r = runner.invoke(cli, [sub, "--help"])
    assert r.exit_code == 0


# ---------- 未登录早退 ----------


def test_me_unauth_exits_1(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["me"])
    assert r.exit_code == 1
    assert "未登录" in r.output


def test_profile_show_unauth(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["profile", "show"])
    assert r.exit_code == 1


def test_query_unauth(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["query", "gender=f"])
    assert r.exit_code == 1


def test_envelope_state_unauth(runner):
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["envelope", "state"])
    assert r.exit_code == 1


def test_logout_without_session_still_ok(runner):
    """logout 无 session 也应干净返回。"""
    from xiangqin.cli import cli

    r = runner.invoke(cli, ["logout"])
    assert r.exit_code == 0
    assert "已登出" in r.output


# ---------- client_session ----------


def test_session_save_load_clear(tmp_path):
    from xiangqin import client_session

    client_session.save(
        session_token="tok",
        user_id="u1",
        endpoint="http://x",
        masked_phone="138****0000",
    )
    s = client_session.load()
    assert s["session_token"] == "tok"
    assert s["user_id"] == "u1"

    client_session.clear()
    assert client_session.load() is None


def test_session_load_missing_returns_none(tmp_path):
    from xiangqin import client_session

    assert client_session.load() is None


# ---------- e2e CLI：用 ASGI transport 直打 app ----------


@pytest.fixture
def asgi_runner(runner, monkeypatch):
    """monkeypatch cli._client 让 httpx 走 FastAPI TestClient，不出进程。"""
    from fastapi.testclient import TestClient

    from xiangqin import cli as cli_mod
    from xiangqin.app import app

    def _make(endpoint: str, token: str | None = None):
        c = TestClient(app)
        if token:
            c.headers["Authorization"] = f"Bearer {token}"
        return c

    monkeypatch.setattr(cli_mod, "_client", _make)
    return runner


def test_cli_health(asgi_runner):
    from xiangqin.cli import cli

    r = asgi_runner.invoke(cli, ["health"])
    assert r.exit_code == 0
    assert "ok" in r.output


def test_cli_register_verify_me(asgi_runner, tmp_path):
    from xiangqin.cli import cli

    r1 = asgi_runner.invoke(cli, ["register", "13800006001"])
    assert r1.exit_code == 0
    # 解析 request_id
    req_id = None
    for line in r1.output.splitlines():
        if line.startswith("request_id:"):
            req_id = line.split(":", 1)[1].strip()
    assert req_id

    r2 = asgi_runner.invoke(cli, ["verify", "123456", "--request-id", req_id])
    assert r2.exit_code == 0

    r3 = asgi_runner.invoke(cli, ["me"])
    assert r3.exit_code == 0

    r4 = asgi_runner.invoke(cli, ["profile", "set", "gender", "f"])
    assert r4.exit_code == 0
    r5 = asgi_runner.invoke(cli, ["profile", "set", "age", "28"])
    assert r5.exit_code == 0
    r6 = asgi_runner.invoke(cli, ["profile", "set", "tags", "hike,code"])
    assert r6.exit_code == 0
    r7 = asgi_runner.invoke(cli, ["profile", "show"])
    assert r7.exit_code == 0
    assert "28" in r7.output

    r8 = asgi_runner.invoke(cli, ["profile", "set", "age", "not-a-number"])
    assert r8.exit_code == 1

    r9 = asgi_runner.invoke(cli, ["profile", "set", "unknown-field", "x"])
    assert r9.exit_code == 1

    r10 = asgi_runner.invoke(cli, ["profile", "clear", "tags"])
    assert r10.exit_code == 0

    r11 = asgi_runner.invoke(cli, ["query", "gender=f"])
    assert r11.exit_code == 0
    r12 = asgi_runner.invoke(cli, ["query", "gender=f", "--json"])
    assert r12.exit_code == 0

    r13 = asgi_runner.invoke(cli, ["envelope", "state"])
    assert r13.exit_code == 0
    assert "免费剩余" in r13.output

    r14 = asgi_runner.invoke(cli, ["envelope", "buy", "--count", "5", "--mock"])
    assert r14.exit_code == 0

    r15 = asgi_runner.invoke(cli, ["blocklist"])
    assert r15.exit_code == 0

    r16 = asgi_runner.invoke(cli, ["logout"])
    assert r16.exit_code == 0
