"""Integration tests for /api/campaigns/{id}/analysis (v0.7 ticket 03)."""

import pytest
from agenteval.models import (
    CampaignStatus,
    Case,
    CaseType,
    ChannelType,
    EvalTarget,
    PlatformType,
    Scenario,
    TargetStatus,
)
from agenteval.storage.db import CampaignAnalysisDB, ModelConfigDB
from agenteval.storage.model_config_repository import ModelConfigRepository
from agenteval.storage.repository import CampaignRepository, ScenarioRepository, TargetRepository
from agenteval.web.app import app
from httpx import ASGITransport, AsyncClient
from sqlmodel import select


@pytest.fixture()
def seeded_db(db_session, monkeypatch):
    from agenteval.storage import db as db_module
    from agenteval.storage import repository as repo_module
    from agenteval.web import app as app_module
    from agenteval.web.routers import campaigns as campaigns_module

    monkeypatch.setattr(app_module, "init_db", lambda: None)
    monkeypatch.setattr(campaigns_module.campaign_runtime, "start", lambda *a, **k: None)

    monkeypatch.setattr(db_module, "get_session", lambda: db_session)
    monkeypatch.setattr(repo_module, "get_session", lambda: db_session)

    from agenteval.web.deps import get_db

    def _test_get_db():
        try:
            yield db_session
        finally:
            pass

    app.dependency_overrides[get_db] = _test_get_db

    TargetRepository(db_session).create(EvalTarget(
        id="t-1", name="mock-target",
        platform=PlatformType.AI_DIGITAL_EMPLOYEE,
        channel_type=ChannelType.TUTU_API,
        channel_config={"base_url": "http://mock", "token": "x"},
        status=TargetStatus.ACTIVE,
    ))
    ScenarioRepository(db_session).create(Scenario(
        id="s-1", name="mock-scenario",
        cases=[Case(id="c1", type=CaseType.SINGLE, messages=["hi"])],
    ))

    yield db_session

    app.dependency_overrides.clear()


@pytest.fixture()
async def client():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as c:
        yield c


def _payload() -> dict:
    return {
        "name": "24h-cycle",
        "target_id": "t-1",
        "window_seconds": 86400,
        "time_scale": 1.0,
        "plan": [{"scenario_id": "s-1", "offset_seconds": 0, "count": 1}],
    }


async def _create_campaign(client, session, status: CampaignStatus = CampaignStatus.COMPLETED) -> str:
    campaign_id = (await client.post("/api/campaigns", json=_payload())).json()["id"]
    repo = CampaignRepository(session)
    campaign = repo.get(campaign_id)
    campaign.status = status
    repo.update(campaign)
    return campaign_id


def _seed_analysis_default(session) -> None:
    ModelConfigRepository(session).create(ModelConfigDB(
        id="mc-1", name="analysis-cfg", provider="openai_compatible", capability="chat",
        endpoint_url="https://models.example.com/v1/chat/completions", model_name="m",
        is_analysis_default=True,
    ))


def _complete_analysis_row(session, campaign_id: str) -> None:
    from agenteval.storage.db import utc_now

    row = CampaignAnalysisDB(
        campaign_id=campaign_id, status="completed", model_config_id="mc-1",
        triggered_by="manual", updated_at=utc_now(),
    )
    row.set_result({
        "overall": "整体达标",
        "problems": [],
        "scenario_narratives": [{"scenario_id": "s-1", "narrative": "表现稳定"}],
        "suggestions": [{"priority": 1, "text": "保持"}],
    })
    existing = session.exec(
        select(CampaignAnalysisDB).where(CampaignAnalysisDB.campaign_id == campaign_id)
    ).first()
    if existing:
        session.delete(existing)
        session.commit()
    session.add(row)
    session.commit()


async def test_get_analysis_empty_state(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db)
    resp = await client.get(f"/api/campaigns/{campaign_id}/analysis")
    assert resp.status_code == 200
    assert resp.json() == {"status": "none"}


async def test_post_analysis_rejects_non_terminal_campaign(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db, status=CampaignStatus.RUNNING)
    resp = await client.post(f"/api/campaigns/{campaign_id}/analysis")
    assert resp.status_code == 400


async def test_post_analysis_rejects_planned_campaign(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db, status=CampaignStatus.PLANNED)
    resp = await client.post(f"/api/campaigns/{campaign_id}/analysis")
    assert resp.status_code == 400


async def test_post_analysis_without_model_guides_configuration(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db)
    resp = await client.post(f"/api/campaigns/{campaign_id}/analysis")
    assert resp.status_code == 400
    assert "分析" in resp.json()["detail"]


async def test_post_then_get_completed_analysis(client, seeded_db, monkeypatch):
    from agenteval.web.routers import campaigns as campaigns_module

    _seed_analysis_default(seeded_db)
    campaign_id = await _create_campaign(client, seeded_db)

    # 假后台任务：同步写入 completed 行（真任务的单测覆盖在 test_campaign_analysis.py）
    monkeypatch.setattr(
        campaigns_module,
        "enqueue_campaign_analysis",
        lambda cid, *, triggered_by: _complete_analysis_row(seeded_db, cid),
    )

    resp = await client.post(f"/api/campaigns/{campaign_id}/analysis")
    assert resp.status_code == 200

    got = (await client.get(f"/api/campaigns/{campaign_id}/analysis")).json()
    assert got["status"] == "completed"
    assert got["model_config_id"] == "mc-1"
    assert got["result"]["overall"] == "整体达标"
    assert got["result"]["scenario_narratives"][0]["narrative"] == "表现稳定"


async def test_rerun_upserts_without_new_row(client, seeded_db, monkeypatch):
    from agenteval.web.routers import campaigns as campaigns_module

    _seed_analysis_default(seeded_db)
    campaign_id = await _create_campaign(client, seeded_db)
    monkeypatch.setattr(
        campaigns_module,
        "enqueue_campaign_analysis",
        lambda cid, *, triggered_by: _complete_analysis_row(seeded_db, cid),
    )

    await client.post(f"/api/campaigns/{campaign_id}/analysis")
    await client.post(f"/api/campaigns/{campaign_id}/analysis")

    rows = seeded_db.exec(
        select(CampaignAnalysisDB).where(CampaignAnalysisDB.campaign_id == campaign_id)
    ).all()
    assert len(rows) == 1


async def test_get_analysis_missing_campaign_404(client, seeded_db):
    resp = await client.get("/api/campaigns/nope/analysis")
    assert resp.status_code == 404


async def test_markdown_export_includes_completed_analysis(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db)
    _complete_analysis_row(seeded_db, campaign_id)
    resp = await client.get(f"/api/campaigns/{campaign_id}/report/markdown")
    assert resp.status_code == 200
    assert "## 智能分析" in resp.text
    assert "整体达标" in resp.text
    assert "表现稳定" in resp.text


async def test_markdown_export_ignores_non_completed_analysis(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db)
    row = CampaignAnalysisDB(campaign_id=campaign_id, status="failed", error="模型超时")
    seeded_db.add(row)
    seeded_db.commit()
    resp = await client.get(f"/api/campaigns/{campaign_id}/report/markdown")
    assert resp.status_code == 200
    assert "智能分析" not in resp.text


async def test_markdown_export_without_analysis_unchanged(client, seeded_db):
    campaign_id = await _create_campaign(client, seeded_db)
    resp = await client.get(f"/api/campaigns/{campaign_id}/report/markdown")
    assert resp.status_code == 200
    assert "智能分析" not in resp.text
