"""Operator-facing HTTP and CLI surface for retrieval learning."""

from __future__ import annotations

import asyncio
import httpx
import json
import sqlite3
from click.testing import CliRunner
import pytest

from logos_adk.agent import Agent
from logos_adk.cli import cli
from logos_adk.config_parser import parse_agent_config_data
from logos_adk.context import RunContext, use_run_context
from logos_adk.learning import FeedbackAPI, RetrievalLearningEngine, RunRecord, SQLiteLearningStore
from logos_adk.providers.mock import MockProvider
from logos_adk.rag.knowledge_base import KnowledgeBase
from logos_adk.rag.vector_store import InMemoryVectorStore


async def _request(app, method: str, path: str, **kwargs):
    transport = httpx.ASGITransport(app=app)
    async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
        return await client.request(method, path, **kwargs)


async def _embed(text: str) -> list[float]:
    normalized = text.lower()
    if "refund" in normalized or "return" in normalized:
        return [1.0, 0.0]
    return [0.0, 1.0]


async def _seed_retrieval_learning(
    engine: RetrievalLearningEngine, learning_store: SQLiteLearningStore, kb: KnowledgeBase
) -> None:
    feedback_api = FeedbackAPI(learning_store)
    await kb.ingest_text(
        "Refund policy from old connector",
        metadata={
            "topic": "refund",
            "source_key": "bad-doc",
            "connector": "notion",
            "workspace_id": "acme",
        },
    )
    await kb.ingest_text(
        "Accurate refund policy from curated connector",
        metadata={
            "topic": "refund",
            "source_key": "good-doc",
            "connector": "confluence",
            "workspace_id": "acme",
        },
    )

    learning_store.create_run(
        RunRecord(
            id="run-1",
            trace_id="trace-1",
            request_id="req-1",
            agent_name="bot",
            input_text="refund",
            output_text="answer",
            session_id="acme:s-1",
            workspace_id="acme",
            prompt_version="v1",
            model="mock",
            config_fingerprint="cfg-1",
        )
    )
    with use_run_context(
        RunContext(
            trace_id="trace-1",
            session_id="acme:s-1",
            workspace_id="acme",
            task_type="support",
            user_segment="vip",
            prompt_name="bot",
            prompt_version="v1",
        )
    ):
        await kb.search("refund", limit=2, filter={"topic": "refund"})

    feedback_api.record_feedback(
        trace_id="trace-1",
        kind="thumbs_down",
        failure_types=["bad_retrieval"],
        metadata={"rejected_contexts": ["bad-doc"]},
    )
    feedback_api.record_outcome(
        trace_id="trace-1",
        kind="conversion",
        value=True,
        metadata={"accepted_contexts": ["good-doc"]},
    )
    engine.sync_trace("trace-1", learning_store)


async def _wait_for_request_status(app, request_id: str, expected_status: str, *, limit: int = 20):
    payload = None
    for _ in range(limit):
        response = await _request(app, "GET", f"/learning/retrieval/reindex-requests/{request_id}")
        assert response.status_code == 200
        payload = response.json()
        if payload["request"]["status"] == expected_status:
            break
        await asyncio.sleep(0.02)
    assert payload is not None
    return payload


@pytest.mark.asyncio
async def test_http_surface_exposes_retrieval_learning_state(tmp_path):
    from logos_adk.serve import create_app

    engine = RetrievalLearningEngine(str(tmp_path / "retrieval.db"))
    learning_store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    await _seed_retrieval_learning(engine, learning_store, kb)

    agent = Agent("bot", model=MockProvider(responses=["answer"]))
    agent.knowledge(kb, mode="memory", limit=2)
    app = create_app([agent], learning_store=learning_store)

    source_scores = await _request(
        app, "GET", "/learning/retrieval/source-scores?workspace_id=acme"
    )
    triggers = await _request(app, "GET", "/learning/retrieval/reindex-triggers?workspace_id=acme")
    profiles = await _request(
        app,
        "GET",
        "/learning/retrieval/profiles?workspace_id=acme&task_type=support&user_segment=vip",
    )

    assert source_scores.status_code == 200
    assert triggers.status_code == 200
    assert profiles.status_code == 200

    source_payload = source_scores.json()
    trigger_payload = triggers.json()
    profile_payload = profiles.json()

    assert source_payload["items"][0]["source_key"] == "good-doc"
    assert any(item["source_key"] == "bad-doc" for item in trigger_payload["items"])
    assert profile_payload["items"][0]["limit"] == 2
    assert profile_payload["items"][0]["filter"] == {"topic": "refund"}

    trigger_id = next(
        item["id"] for item in trigger_payload["items"] if item["source_key"] == "bad-doc"
    )
    ack = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-triggers/{trigger_id}/ack",
        json={"note": "investigating"},
    )
    resolve = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-triggers/{trigger_id}/resolve",
        json={"note": "reindex queued"},
    )
    force = await _request(
        app,
        "POST",
        "/learning/retrieval/force-reindex",
        json={
            "workspace_id": "acme",
            "source_key": "bad-doc",
            "reason": "manual_refresh",
            "requested_by": "ops",
            "metadata": {"ticket": "INC-7"},
        },
    )
    queued_request = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="queued-coalesce-doc",
        reason="manual_refresh",
        requested_by="ops-preseed",
    )
    coalesced_force = await _request(
        app,
        "POST",
        "/learning/retrieval/force-reindex",
        json={
            "workspace_id": "acme",
            "source_key": "queued-coalesce-doc",
            "reason": "manual_refresh",
            "requested_by": "ops-2",
        },
    )
    export_csv = await _request(
        app,
        "GET",
        "/learning/retrieval/source-scores/export?workspace_id=acme&format=csv",
    )

    assert ack.status_code == 200
    assert ack.json()["trigger"]["status"] == "acknowledged"
    assert resolve.status_code == 200
    assert resolve.json()["trigger"]["status"] == "resolved"
    assert force.status_code == 200
    assert coalesced_force.status_code == 200
    assert force.json()["request"]["status"] == "queued"
    assert force.json()["request"]["source_key"] == "bad-doc"
    force_request_id = force.json()["request"]["id"]
    assert coalesced_force.json()["request"]["id"] == queued_request.id
    assert coalesced_force.json()["request"]["metadata"]["coalesced_count"] >= 1
    assert export_csv.status_code == 200
    assert "source_key,connector,usefulness_score" in export_csv.text
    assert "good-doc" in export_csv.text

    detail_payload = await _wait_for_request_status(app, force_request_id, "completed")
    requests_response = await _request(
        app, "GET", "/learning/retrieval/reindex-requests?workspace_id=acme"
    )
    assert requests_response.status_code == 200
    requests_payload = requests_response.json()
    current = next(item for item in requests_payload["items"] if item["id"] == force_request_id)
    assert current["status"] == "completed"
    assert current["metadata"]["documents_reindexed"] >= 1
    assert detail_payload["request"]["id"] == force_request_id
    assert detail_payload["request"]["status"] == "completed"

    metrics_requests = await _request(
        app, "GET", "/observability/metrics?name=adk_reindex_requests_total"
    )
    metrics_coalesced = await _request(
        app, "GET", "/observability/metrics?name=adk_reindex_coalesced_total"
    )
    metrics_duration = await _request(
        app, "GET", "/observability/metrics?name=adk_reindex_duration_ms"
    )
    assert metrics_requests.status_code == 200
    assert metrics_coalesced.status_code == 200
    assert metrics_duration.status_code == 200
    assert any(
        item["name"] == "adk_reindex_requests_total" for item in metrics_requests.json()["items"]
    )
    assert any(
        item["name"] == "adk_reindex_coalesced_total" for item in metrics_coalesced.json()["items"]
    )
    assert any(
        item["name"] == "adk_reindex_duration_ms" for item in metrics_duration.json()["items"]
    )

    source_documents = await _request(
        app,
        "GET",
        "/learning/retrieval/source-documents?workspace_id=acme&source_key=good-doc",
    )
    dashboard = await _request(app, "GET", "/observability/grafana/dashboard")
    summary = await _request(app, "GET", "/observability/summary")

    assert source_documents.status_code == 200
    assert source_documents.json()["items"][0]["source_key"] == "good-doc"
    assert source_documents.json()["items"][0]["content_length"] > 0
    assert dashboard.status_code == 200
    assert any(panel["title"] == "Reindex Failures" for panel in dashboard.json()["panels"])
    assert any(
        panel["title"] == "Reindex Coalesced Requests" for panel in dashboard.json()["panels"]
    )
    assert any(
        panel["title"] == "Reindex Worker Latency P95" for panel in dashboard.json()["panels"]
    )
    assert summary.status_code == 200
    assert any(
        rule["metric_name"] == "adk_reindex_failures_total" for rule in summary.json()["rules"]
    )
    assert any(rule["metric_name"] == "adk_reindex_duration_ms" for rule in summary.json()["rules"])


@pytest.mark.asyncio
async def test_http_reindex_retry_failed_request_and_failure_metrics(tmp_path):
    from logos_adk.serve import create_app

    engine = RetrievalLearningEngine(str(tmp_path / "retrieval.db"))
    learning_store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    agent = Agent("bot", model=MockProvider(responses=["answer"]))
    agent.knowledge(kb, mode="memory", limit=2)
    app = create_app([agent], learning_store=learning_store)

    failed_force = await _request(
        app,
        "POST",
        "/learning/retrieval/force-reindex",
        json={
            "workspace_id": "acme",
            "source_key": "missing-doc",
            "reason": "manual_refresh",
            "requested_by": "ops",
        },
    )
    assert failed_force.status_code == 200
    request_id = failed_force.json()["request"]["id"]

    failed_detail = await _wait_for_request_status(app, request_id, "failed")
    assert failed_detail["request"]["metadata"]["error"] == "source_snapshot_missing"

    await kb.ingest_text(
        "Recovered refund source",
        metadata={"topic": "refund", "source_key": "missing-doc", "workspace_id": "acme"},
    )

    retry = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{request_id}/retry",
        json={"note": "snapshot restored"},
    )
    assert retry.status_code == 200
    assert retry.json()["request"]["status"] == "queued"

    completed_detail = await _wait_for_request_status(app, request_id, "completed")
    assert completed_detail["request"]["metadata"]["documents_reindexed"] >= 1
    assert completed_detail["request"]["metadata"]["retry_count"] == 1

    failure_metrics = await _request(
        app, "GET", "/observability/metrics?name=adk_reindex_failures_total"
    )
    request_metrics = await _request(
        app, "GET", "/observability/metrics?name=adk_reindex_requests_total"
    )
    alerts = await _request(app, "GET", "/observability/alerts")
    assert failure_metrics.status_code == 200
    assert request_metrics.status_code == 200
    assert alerts.status_code == 200
    assert any(
        item["name"] == "adk_reindex_failures_total" for item in failure_metrics.json()["items"]
    )
    assert len(request_metrics.json()["items"]) >= 2
    assert any(item["rule_name"] == "reindex_failure_spike" for item in alerts.json()["items"])


@pytest.mark.asyncio
async def test_http_cancel_queued_reindex_request_and_cli_source_documents(tmp_path):
    from logos_adk.serve import create_app

    engine = RetrievalLearningEngine(str(tmp_path / "retrieval.db"))
    learning_store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    await kb.ingest_text(
        "Refund source snapshot",
        metadata={"topic": "refund", "source_key": "cancel-doc", "workspace_id": "acme"},
    )
    request = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="cancel-doc",
        reason="operator_requested",
        requested_by="ops",
    )

    agent = Agent("bot", model=MockProvider(responses=["answer"]))
    agent.knowledge(kb, mode="memory", limit=2)
    app = create_app([agent], learning_store=learning_store)

    cancel = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{request.id}/cancel",
        json={"note": "no longer needed"},
    )
    source_documents = await _request(
        app,
        "GET",
        "/learning/retrieval/source-documents?workspace_id=acme&source_key=cancel-doc",
    )

    assert cancel.status_code == 200
    assert cancel.json()["request"]["status"] == "canceled"
    assert source_documents.status_code == 200
    assert source_documents.json()["items"][0]["source_key"] == "cancel-doc"
    assert source_documents.json()["items"][0]["content_length"] > 0

    runner = CliRunner()
    source_docs_cli = runner.invoke(
        cli,
        [
            "retrieval",
            "source-documents",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "acme",
            "--source-key",
            "cancel-doc",
        ],
    )
    cancel_cli = runner.invoke(
        cli,
        [
            "retrieval",
            "cancel-request",
            request.id,
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--note",
            "already canceled",
        ],
    )
    assert source_docs_cli.exit_code == 0
    assert "cancel-doc" in source_docs_cli.output
    assert cancel_cli.exit_code != 0


@pytest.mark.asyncio
async def test_engine_source_document_retention_and_reindex_coalescing(tmp_path):
    engine = RetrievalLearningEngine(
        str(tmp_path / "retrieval.db"),
        max_source_documents_per_source=1,
    )
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    await kb.ingest_text(
        "Older source snapshot",
        metadata={"topic": "refund", "source_key": "coalesce-doc", "workspace_id": "acme"},
    )
    await kb.ingest_text(
        "Latest source snapshot",
        metadata={"topic": "refund", "source_key": "coalesce-doc", "workspace_id": "acme"},
    )

    documents = engine.list_source_documents(workspace_id="acme", source_key="coalesce-doc")
    assert len(documents) == 1
    assert documents[0].content == "Latest source snapshot"

    await kb.ingest_text(
        "TTL source snapshot",
        metadata={"topic": "refund", "source_key": "ttl-doc", "workspace_id": "acme"},
    )
    connection = sqlite3.connect(str(tmp_path / "retrieval.db"))
    try:
        connection.execute(
            "UPDATE retrieval_learning_source_documents SET updated_at = ? WHERE workspace_id = ? AND source_key = ?",
            ("2000-01-01T00:00:00+00:00", "acme", "ttl-doc"),
        )
        connection.commit()
    finally:
        connection.close()
    deleted = engine.prune_source_documents(workspace_id="acme", source_key="ttl-doc", ttl_days=1)
    assert deleted >= 1
    assert engine.list_source_documents(workspace_id="acme", source_key="ttl-doc") == []

    first = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="coalesce-doc",
        reason="manual_refresh",
        requested_by="ops-a",
    )
    second = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="coalesce-doc",
        reason="manual_refresh",
        requested_by="ops-b",
    )

    assert first.id == second.id
    assert second.metadata["coalesced_count"] >= 1
    assert "ops-b" in second.metadata["coalesced_requesters"]


def test_engine_reads_retrieval_policy_from_env_and_agent_config(tmp_path, monkeypatch):
    monkeypatch.setenv("LOGOS_ADK_RETRIEVAL_SOURCE_TTL_DAYS", "9")
    monkeypatch.setenv("LOGOS_ADK_RETRIEVAL_MAX_SOURCE_DOCUMENTS", "3")
    monkeypatch.setenv("LOGOS_ADK_RETRIEVAL_DEDUPE_WINDOW_SECONDS", "45")

    env_engine = RetrievalLearningEngine(str(tmp_path / "env.db"))
    assert env_engine.source_document_ttl_days == 9
    assert env_engine.max_source_documents_per_source == 3
    assert env_engine.dedupe_window_seconds == 45

    agent_config = parse_agent_config_data(
        {
            "name": "cfg-bot",
            "model": "mock",
            "learning": {
                "retrieval": {
                    "dsn": "postgresql://user:pass@localhost/retrieval",
                    "path": str(tmp_path / "cfg.db"),
                    "table_prefix": "cfg_rl",
                    "source_document_ttl_days": 12,
                    "max_source_documents_per_source": 6,
                    "dedupe_window_seconds": 180,
                }
            },
        }
    )
    cfg_engine = RetrievalLearningEngine.from_config(agent_config)
    assert cfg_engine.dsn == "postgresql://user:pass@localhost/retrieval"
    assert cfg_engine.path == str(tmp_path / "cfg.db")
    assert cfg_engine.table_prefix == "cfg_rl"
    assert cfg_engine.source_document_ttl_days == 12
    assert cfg_engine.max_source_documents_per_source == 6
    assert cfg_engine.dedupe_window_seconds == 180


@pytest.mark.asyncio
async def test_http_resume_replay_and_summary_for_canceled_requests(tmp_path):
    from logos_adk.serve import create_app

    engine = RetrievalLearningEngine(str(tmp_path / "retrieval.db"))
    learning_store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    await kb.ingest_text(
        "Replayable refund source",
        metadata={"topic": "refund", "source_key": "resume-doc", "workspace_id": "acme"},
    )
    await kb.ingest_text(
        "Replayable refund source two",
        metadata={"topic": "refund", "source_key": "replay-doc", "workspace_id": "acme"},
    )

    agent = Agent("bot", model=MockProvider(responses=["answer"]))
    agent.knowledge(kb, mode="memory", limit=2)
    app = create_app([agent], learning_store=learning_store)

    queued = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="resume-doc",
        reason="operator_requested",
        requested_by="ops",
    )
    cancel = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{queued.id}/cancel",
        json={"note": "paused"},
    )
    assert cancel.status_code == 200
    assert cancel.json()["request"]["status"] == "canceled"

    resume = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{queued.id}/resume",
        json={"note": "resume now"},
    )
    assert resume.status_code == 200
    assert resume.json()["request"]["status"] == "queued"
    resumed_detail = await _request(
        app,
        "GET",
        f"/learning/retrieval/reindex-requests/{queued.id}",
    )
    assert resumed_detail.status_code == 200
    assert resumed_detail.json()["request"]["metadata"]["resume_count"] == 1

    queued_again = engine.create_force_reindex_request(
        workspace_id="acme",
        source_key="replay-doc",
        reason="operator_requested",
        requested_by="ops",
    )
    cancel_again = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{queued_again.id}/cancel",
        json={"note": "pause again"},
    )
    assert cancel_again.status_code == 200

    replay = await _request(
        app,
        "POST",
        f"/learning/retrieval/reindex-requests/{queued_again.id}/replay",
        json={"note": "fresh replay"},
    )
    assert replay.status_code == 200
    replay_id = replay.json()["request"]["id"]
    assert replay_id != queued_again.id
    replayed_detail = await _request(
        app,
        "GET",
        f"/learning/retrieval/reindex-requests/{replay_id}",
    )
    assert replayed_detail.status_code == 200
    assert replayed_detail.json()["request"]["metadata"]["replay_of"] == queued_again.id

    summary = await _request(app, "GET", "/learning/retrieval/summary?workspace_id=acme")
    assert summary.status_code == 200
    payload = summary.json()
    assert payload["requests"]["total"] >= 3
    assert payload["triggers"]["total"] >= 0
    assert payload["source_documents"]["distinct_sources"] >= 1
    assert payload["policy"]["dedupe_window_seconds"] >= 0
    assert "coalesced_total" in payload["coalescing"]


@pytest.mark.asyncio
async def test_cli_surface_prints_current_retrieval_learning_state(tmp_path):
    engine = RetrievalLearningEngine(str(tmp_path / "retrieval.db"))
    learning_store = SQLiteLearningStore(str(tmp_path / "learning.db"))
    kb = KnowledgeBase(
        store=InMemoryVectorStore(),
        embed_fn=_embed,
        retrieval_learning=engine,
    )
    await _seed_retrieval_learning(engine, learning_store, kb)

    runner = CliRunner()
    source_scores_output = tmp_path / "reports" / "source-scores.json"
    source_scores = runner.invoke(
        cli,
        [
            "retrieval",
            "source-scores",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "acme",
            "--output",
            str(source_scores_output),
        ],
    )
    triggers = runner.invoke(
        cli,
        [
            "retrieval",
            "reindex-triggers",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "acme",
        ],
    )
    profiles = runner.invoke(
        cli,
        [
            "retrieval",
            "profiles",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "acme",
            "--task-type",
            "support",
            "--user-segment",
            "vip",
        ],
    )
    trigger_id = engine.list_reindex_triggers(workspace_id="acme")[0].id
    ack = runner.invoke(
        cli,
        [
            "retrieval",
            "ack-trigger",
            trigger_id,
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--note",
            "seen",
        ],
    )
    resolve = runner.invoke(
        cli,
        [
            "retrieval",
            "resolve-trigger",
            trigger_id,
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--note",
            "resolved",
        ],
    )
    close = runner.invoke(
        cli,
        [
            "retrieval",
            "close-trigger",
            trigger_id,
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--note",
            "done",
        ],
    )

    assert source_scores.exit_code == 0
    assert triggers.exit_code == 0
    assert profiles.exit_code == 0
    assert ack.exit_code == 0
    assert resolve.exit_code == 0
    assert close.exit_code == 0
    assert "good-doc" in source_scores.output
    assert "bad-doc" in triggers.output
    assert 'filter={"topic": "refund"}' in profiles.output
    assert "status=acknowledged" in ack.output
    assert "status=resolved" in resolve.output
    assert "status=closed" in close.output
    payload = json.loads(source_scores_output.read_text())
    assert payload["workspace_id"] == "acme"
    assert any(item["source_key"] == "good-doc" for item in payload["items"])


def test_cli_surface_reports_empty_retrieval_learning_state(tmp_path):
    runner = CliRunner()
    empty_output = tmp_path / "reports" / "empty-source-scores.json"

    source_scores = runner.invoke(
        cli,
        [
            "retrieval",
            "source-scores",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "empty",
            "--output",
            str(empty_output),
        ],
    )
    triggers = runner.invoke(
        cli,
        [
            "retrieval",
            "reindex-triggers",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "empty",
        ],
    )
    profiles = runner.invoke(
        cli,
        [
            "retrieval",
            "profiles",
            "--db-path",
            str(tmp_path / "retrieval.db"),
            "--workspace-id",
            "empty",
        ],
    )

    assert source_scores.exit_code == 0
    assert triggers.exit_code == 0
    assert profiles.exit_code == 0
    assert "No source scores found." in source_scores.output
    assert "No reindex triggers found." in triggers.output
    assert "No retrieval profiles found." in profiles.output
    payload = json.loads(empty_output.read_text())
    assert payload == {"workspace_id": "empty", "items": []}


def test_cli_surface_rejects_unknown_reindex_trigger_ids(tmp_path):
    runner = CliRunner()

    ack = runner.invoke(
        cli,
        [
            "retrieval",
            "ack-trigger",
            "missing-trigger",
            "--db-path",
            str(tmp_path / "retrieval.db"),
        ],
    )
    resolve = runner.invoke(
        cli,
        [
            "retrieval",
            "resolve-trigger",
            "missing-trigger",
            "--db-path",
            str(tmp_path / "retrieval.db"),
        ],
    )
    close = runner.invoke(
        cli,
        [
            "retrieval",
            "close-trigger",
            "missing-trigger",
            "--db-path",
            str(tmp_path / "retrieval.db"),
        ],
    )

    assert ack.exit_code != 0
    assert resolve.exit_code != 0
    assert close.exit_code != 0
    assert "Unknown reindex trigger: missing-trigger" in ack.output
    assert "Unknown reindex trigger: missing-trigger" in resolve.output
    assert "Unknown reindex trigger: missing-trigger" in close.output
