"""
tests/test_assistant.py — 工作台 AI 助手入口（方案 A+B 后端）。

覆盖：
- GET /status/recent-runs: 跨智能体最近运行、运行中排最前、租户隔离。
- POST /assistant/route: 唯一智能体直返、分类命中、拿不准回退、
  分类器异常降级、choice 越界保护（注入 fake 分类器，零 LLM 调用）。
"""
from __future__ import annotations

import json
import os

import pytest

os.environ.setdefault("AGENTPAAS_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AGENTPAAS_TESTING", "1")


@pytest.fixture()
def api_client(tmp_path, monkeypatch):
    import secrets
    from fastapi.testclient import TestClient
    from agentpaas.api.app import app
    from agentpaas.api.middleware.auth import hash_key
    from agentpaas.config import settings
    from agentpaas.db.models import Database, gen_id, now_utc
    import agentpaas.db.session as _session_mod

    monkeypatch.setattr(settings, "workspace_base", str(tmp_path / "Workspace"))
    prev = _session_mod._db
    _session_mod._db = Database("sqlite:///:memory:")
    db = _session_mod._db
    tid, uid = gen_id("tn_"), gen_id("usr_")
    raw_key = f"ap_{secrets.token_hex(16)}"
    now = now_utc()
    db.execute("INSERT INTO tenants (id, name, plan, status, created_at) "
               "VALUES (?, 'test', 'free', 'active', ?)", (tid, now))
    db.execute("INSERT INTO users (id, tenant_id, email, role, created_at) "
               "VALUES (?, ?, '', 'admin', ?)", (uid, tid, now))
    db.execute(
        "INSERT INTO api_keys (id, tenant_id, user_id, key_hash, key_prefix, name, "
        "scopes, rate_limit, status, created_at) "
        "VALUES (?, ?, ?, ?, ?, 'test', ?, 600, 'active', ?)",
        (gen_id("key_"), tid, uid, hash_key(raw_key), raw_key[:8],
         json.dumps(["agents:*"]), now))
    db.commit()
    with TestClient(app) as client:
        yield client, raw_key, tid, db
    _session_mod._db = prev


def _auth(k):
    return {"Authorization": f"Bearer {k}"}


def _mk_agent(client, key, name, desc=""):
    cfg = {"name": name, "type": "simple",
           "model": {"name": "ollama/qwen2.5:7b"}, "systemPrompt": "t"}
    r = client.post("/api/v1/agents", headers=_auth(key),
                    json={"name": name, "description": desc, "config": cfg})
    assert r.status_code in (200, 201), r.text[:200]
    return r.json()["agent_id"]


# ── recent-runs ──────────────────────────────────────────────────────────────

def test_recent_runs_running_first_and_truncated(api_client):
    from agentpaas.db.models import gen_id, now_utc
    client, key, tid, db = api_client
    aid = _mk_agent(client, key, "甲")
    now = now_utc()
    for i, status in enumerate(["completed", "running", "failed"]):
        db.execute(
            "INSERT INTO runs (id, agent_id, agent_version, tenant_id, input, "
            "status, created_at) VALUES (?, ?, 1, ?, ?, ?, ?)",
            (gen_id("run_"), aid, tid, f"输入{i}" + "长" * 200, status, now))
    db.commit()

    r = client.get("/api/v1/status/recent-runs?limit=10", headers=_auth(key))
    assert r.status_code == 200
    items = r.json()["items"]
    assert len(items) == 3
    assert items[0]["status"] == "running"          # 运行中排最前
    assert items[0]["agent_name"] == "甲"
    assert len(items[0]["input_preview"]) <= 120    # 截断


def test_recent_runs_tenant_scoped(api_client):
    from agentpaas.db.models import gen_id, now_utc
    client, key, tid, db = api_client
    # 别的租户的 run 不可见
    other_t = gen_id("tn_")
    db.execute("INSERT INTO tenants (id, name, plan, status, created_at) "
               "VALUES (?, 'o', 'free', 'active', ?)", (other_t, now_utc()))
    other_a = gen_id("ag_")
    db.execute("INSERT INTO agents (id, tenant_id, name, current_version, status, "
               "created_at, updated_at) VALUES (?, ?, 'x', 1, 'active', ?, ?)",
               (other_a, other_t, now_utc(), now_utc()))
    db.execute("INSERT INTO runs (id, agent_id, agent_version, tenant_id, input, "
               "status, created_at) VALUES (?, ?, 1, ?, 'i', 'completed', ?)",
               (gen_id("run_"), other_a, other_t, now_utc()))
    db.commit()
    r = client.get("/api/v1/status/recent-runs", headers=_auth(key))
    assert r.json()["items"] == []


# ── assistant/route ──────────────────────────────────────────────────────────

def test_route_single_agent_no_llm(api_client):
    client, key, *_ = api_client
    aid = _mk_agent(client, key, "唯一助手")
    r = client.post("/api/v1/assistant/route", headers=_auth(key),
                    json={"input": "随便什么任务"})
    assert r.status_code == 200
    d = r.json()
    assert d["matched"] is True and d["agent_id"] == aid


def test_route_classifier_picks(api_client, monkeypatch):
    from agentpaas.api.v1 import assistant as mod
    client, key, *_ = api_client
    _mk_agent(client, key, "审稿助手", "论文评审")
    aid_exam = _mk_agent(client, key, "试卷助手", "出试卷与题库")

    monkeypatch.setattr(mod, "_pick_classifier_model",
                        lambda: {"provider": "fake", "name": "f"})
    seen = {}

    def fake_classify(user_input, candidates, model):
        seen["input"] = user_input
        seen["names"] = [c["name"] for c in candidates]
        # 选「试卷助手」
        idx = next(i for i, c in enumerate(candidates, 1) if c["name"] == "试卷助手")
        return idx, "出题类请求", {"input_tokens": 50, "output_tokens": 10}

    monkeypatch.setattr(mod, "_classify_fn", fake_classify)
    r = client.post("/api/v1/assistant/route", headers=_auth(key),
                    json={"input": "给数据结构出一份期末卷"})
    d = r.json()
    assert d["matched"] is True and d["agent_id"] == aid_exam
    assert d["reason"] == "出题类请求"
    assert "试卷助手" in seen["names"] and "期末卷" in seen["input"]


def test_route_unsure_and_out_of_range(api_client, monkeypatch):
    from agentpaas.api.v1 import assistant as mod
    client, key, *_ = api_client
    _mk_agent(client, key, "甲")
    _mk_agent(client, key, "乙")
    monkeypatch.setattr(mod, "_pick_classifier_model",
                        lambda: {"provider": "fake", "name": "f"})
    # choice=0 拿不准
    monkeypatch.setattr(mod, "_classify_fn", lambda *a: (0, "都不像", None))
    assert client.post("/api/v1/assistant/route", headers=_auth(key),
                       json={"input": "x"}).json()["matched"] is False
    # choice 越界
    monkeypatch.setattr(mod, "_classify_fn", lambda *a: (99, "", None))
    assert client.post("/api/v1/assistant/route", headers=_auth(key),
                       json={"input": "x"}).json()["matched"] is False
    # 分类器抛异常 → 降级 matched=false 而非 500
    def boom(*a):
        raise RuntimeError("provider down")
    monkeypatch.setattr(mod, "_classify_fn", boom)
    r = client.post("/api/v1/assistant/route", headers=_auth(key), json={"input": "x"})
    assert r.status_code == 200 and r.json()["matched"] is False


def test_route_no_classifier_model(api_client, monkeypatch):
    from agentpaas.api.v1 import assistant as mod
    client, key, *_ = api_client
    _mk_agent(client, key, "甲")
    _mk_agent(client, key, "乙")
    monkeypatch.setattr(mod, "_pick_classifier_model", lambda: None)
    r = client.post("/api/v1/assistant/route", headers=_auth(key), json={"input": "x"})
    assert r.json()["matched"] is False
