"""`xq` CLI — 客户端。

0.1 命令：
    xq health
    xq register <phone>
    xq verify <code> --request-id <ULID>
    xq logout
    xq me
"""

from __future__ import annotations

import os

import click
import httpx

from . import __version__, client_session

DEFAULT_ENDPOINT = os.environ.get("XIANGQIN_ENDPOINT", "http://112.124.27.213")


@click.group()
@click.version_option(__version__, prog_name="xq")
@click.option(
    "--endpoint",
    default=None,
    envvar="XIANGQIN_ENDPOINT",
    help="xiangqin 服务端 URL（默认读本地 session 或 http://112.124.27.213）",
)
@click.pass_context
def cli(ctx: click.Context, endpoint: str | None) -> None:
    """xiangqin 客户端 — 相亲 + 付费置顶。"""
    ctx.ensure_object(dict)
    sess = client_session.load()
    if endpoint:
        ctx.obj["endpoint"] = endpoint.rstrip("/")
    elif sess and sess.get("endpoint"):
        ctx.obj["endpoint"] = sess["endpoint"]
    else:
        ctx.obj["endpoint"] = DEFAULT_ENDPOINT
    ctx.obj["session"] = sess


def _client(endpoint: str, token: str | None = None) -> httpx.Client:
    headers = {}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    return httpx.Client(base_url=endpoint, timeout=10.0, headers=headers, trust_env=False)


def _require_session(ctx: click.Context) -> dict[str, str]:
    sess = ctx.obj.get("session")
    if not sess or not sess.get("session_token"):
        click.echo("未登录，先 `xq register <phone>`", err=True)
        raise SystemExit(1)
    return sess


# ========== 基础 ==========


@cli.command()
@click.pass_context
def health(ctx: click.Context) -> None:
    """健康检查。"""
    with _client(ctx.obj["endpoint"]) as c:
        try:
            r = c.get("/health")
            r.raise_for_status()
            click.echo(r.text)
        except httpx.HTTPError as e:
            click.echo(f"health check failed: {e}", err=True)
            raise SystemExit(1) from e


# ========== auth ==========


@cli.command()
@click.argument("phone")
@click.pass_context
def register(ctx: click.Context, phone: str) -> None:
    """发起注册 / 登录（返回 request_id，0.1 期 mock code = 123456）。"""
    with _client(ctx.obj["endpoint"]) as c:
        r = c.post("/auth/register", json={"phone": phone})
    if r.status_code != 200:
        click.echo(f"register failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    data = r.json()
    # 记住 masked_phone 给下一步 verify 做上下文
    ctx.obj["__last_phone_masked"] = data["masked_phone"]
    click.echo(f"验证码已发 {data['masked_phone']}，{data['expires_in_s']}s 内有效")
    click.echo(f"request_id: {data['request_id']}")
    click.echo(f"提示：{data.get('mock_code_hint', '')}")
    # 临时把 register 产生的 masked_phone 暂存（给 verify 显示）
    os.environ["_XIANGQIN_LAST_MASKED"] = data["masked_phone"]


@cli.command()
@click.argument("code")
@click.option("--request-id", required=True, help="register 命令返回的 ULID")
@click.pass_context
def verify(ctx: click.Context, code: str, request_id: str) -> None:
    """用 request_id + 验证码换 session。"""
    with _client(ctx.obj["endpoint"]) as c:
        r = c.post("/auth/verify", json={"request_id": request_id, "code": code})
    if r.status_code != 200:
        click.echo(f"verify failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    data = r.json()
    masked = os.environ.get("_XIANGQIN_LAST_MASKED", "***")
    client_session.save(
        session_token=data["session_token"],
        user_id=data["user_id"],
        endpoint=ctx.obj["endpoint"],
        masked_phone=masked,
    )
    tag = "新用户" if data["is_new_user"] else "老用户"
    click.echo(f"登录成功（{tag}），user_id={data['user_id']}")
    click.echo(f"session 已写本机 ~/.xiangqin/session.json")


@cli.command()
@click.pass_context
def logout(ctx: click.Context) -> None:
    """登出（删本机 session + 服务端 session 行）。"""
    sess = ctx.obj.get("session")
    if sess and sess.get("session_token"):
        try:
            with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
                c.post("/auth/logout")
        except httpx.HTTPError:
            pass  # 本地清理无论如何都走
    client_session.clear()
    click.echo("已登出")


@cli.command()
@click.pass_context
def me(ctx: click.Context) -> None:
    """查当前登录态。"""
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.get("/auth/me")
    if r.status_code != 200:
        click.echo(f"me failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    data = r.json()
    click.echo(f"user_id: {data['user_id']}")
    click.echo(f"phone:   {sess.get('masked_phone', '***')}")
    click.echo(f"endpoint: {ctx.obj['endpoint']}")


# ========== profile ==========


@cli.group()
def profile() -> None:
    """查 / 改个人资料。"""


@profile.command("show")
@click.pass_context
def profile_show(ctx: click.Context) -> None:
    """查看当前用户资料。"""
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.get("/profile/me")
    if r.status_code != 200:
        click.echo(f"profile show failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    data = r.json()
    click.echo(f"user_id: {data['user_id']}")
    click.echo(f"gender:  {data.get('gender') or '（未填）'}")
    click.echo(f"age:     {data.get('age') or '（未填）'}")
    click.echo(f"city:    {data.get('city') or '（未填）'}")
    tags = data.get("tags") or []
    click.echo(f"tags:    {', '.join(tags) if tags else '（未填）'}")
    bio = data.get("bio")
    click.echo(f"bio:     {bio or '（未填）'}")


@profile.command("set")
@click.argument("field")
@click.argument("value")
@click.pass_context
def profile_set(ctx: click.Context, field: str, value: str) -> None:
    """改单个字段：xq profile set <gender|age|city|tags|bio> <value>

    tags 用逗号分隔：`xq profile set tags 程序,登山,做饭`
    """
    sess = _require_session(ctx)
    payload: dict[str, object] = {}
    if field == "age":
        try:
            payload["age"] = int(value)
        except ValueError as e:
            click.echo(f"age 必须是整数，收到 {value!r}", err=True)
            raise SystemExit(1) from e
    elif field == "tags":
        payload["tags"] = [t.strip() for t in value.split(",") if t.strip()]
    elif field in {"gender", "city", "bio"}:
        payload[field] = value
    else:
        click.echo(f"未知字段：{field}（允许 gender/age/city/tags/bio）", err=True)
        raise SystemExit(1)

    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.put("/profile/me", json=payload)
    if r.status_code != 200:
        click.echo(f"profile set failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    click.echo(f"ok: {field} 已更新")


@profile.command("clear")
@click.argument("field", type=click.Choice(["tags", "bio"]))
@click.pass_context
def profile_clear(ctx: click.Context, field: str) -> None:
    """清空可空字段（只支持 tags / bio；gender/age/city 必填不让清）。"""
    sess = _require_session(ctx)
    payload: dict[str, object] = {field: [] if field == "tags" else None}
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.put("/profile/me", json=payload)
    if r.status_code != 200:
        click.echo(f"profile clear failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    click.echo(f"ok: {field} 已清空")


# ========== query ==========


@cli.command()
@click.argument("where")
@click.option("--limit", default=50, show_default=True, type=int, help="最多返回几条（1-100）")
@click.option("--json", "as_json", is_flag=True, help="原始 JSON 输出")
@click.pass_context
def query(ctx: click.Context, where: str, limit: int, as_json: bool) -> None:
    """查匹配。受限 DSL，例：xq query 'gender=f AND city=hangzhou AND age>=25'

    付费用户带 🔥 并置顶。每次命中扣 1 次他们的曝光余额。
    """
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.post("/query", json={"where": where, "limit": limit})
    if r.status_code != 200:
        click.echo(f"query failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    data = r.json()
    if as_json:
        import json as _json
        click.echo(_json.dumps(data, ensure_ascii=False, indent=2))
        return

    hits = data["results"]
    if not hits:
        click.echo("0 条命中")
        return

    click.echo(
        f"{data['total_hits']} 条命中（其中 {data['promoted_hits']} 条付费置顶 🔥）"
    )
    click.echo("-" * 60)
    for h in hits:
        mark = "🔥 " if h["is_promoted"] else "   "
        tags_s = ", ".join(h.get("tags") or [])
        click.echo(
            f"{mark}{h['user_id']}  {h.get('gender') or '?'}  "
            f"{h.get('age') or '?'}  {h.get('city') or '?'}"
        )
        if tags_s:
            click.echo(f"   tags: {tags_s}")
        if h.get("bio"):
            click.echo(f"   bio:  {h['bio']}")


# ========== exposure ==========


@cli.group()
def expose() -> None:
    """曝光（付费置顶）——买 / 查余额 / 查历史。"""


@expose.command("buy")
@click.option("--count", required=True, type=int, help="买几次曝光（1-10000）")
@click.option("--mock", is_flag=True, help="0.1 期必须带此标志（真付 0.2 再开）")
@click.pass_context
def expose_buy(ctx: click.Context, count: int, mock: bool) -> None:
    """下单买曝光。例：xq expose buy --count 100 --mock"""
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.post("/exposure/orders", json={"count": count, "mock": mock})
    if r.status_code != 200:
        click.echo(f"buy failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    d = r.json()
    yuan = d["amount_cents"] / 100
    click.echo(
        f"ok: 订单 {d['id']}，买 {d['count']} 次曝光，¥{yuan:.2f}"
        + (" [mock]" if d["mock"] else "")
    )


@expose.command("balance")
@click.pass_context
def expose_balance(ctx: click.Context) -> None:
    """查曝光余额。"""
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.get("/exposure/balance")
    if r.status_code != 200:
        click.echo(f"balance failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    d = r.json()
    click.echo(f"剩余曝光次数: {d['remaining']}")
    click.echo(f"历史总买   : {d['total_purchased']}")


@expose.command("history")
@click.option("--limit", default=20, type=int, show_default=True)
@click.pass_context
def expose_history(ctx: click.Context, limit: int) -> None:
    """查订单历史。"""
    sess = _require_session(ctx)
    with _client(ctx.obj["endpoint"], sess["session_token"]) as c:
        r = c.get(f"/exposure/history?limit={limit}")
    if r.status_code != 200:
        click.echo(f"history failed: {r.status_code} {r.text}", err=True)
        raise SystemExit(1)
    orders = r.json()
    if not orders:
        click.echo("暂无订单")
        return
    import datetime as _dt
    for o in orders:
        ts = _dt.datetime.fromtimestamp(o["created_at"]).strftime("%Y-%m-%d %H:%M")
        yuan = o["amount_cents"] / 100
        tag = "[mock]" if o["mock"] else "[真付]"
        click.echo(f"{ts}  {tag}  {o['count']} 次曝光  ¥{yuan:.2f}  {o['id']}")
