from uuid import uuid4

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import select

from services.interaction_service.app import app as interaction_app
from services.kb_service.app import app as kb_app
from services.reporting_service.app import app as reporting_app
from services.shared.db import get_session
from services.shared.sql_models import Interaction, ReportingInteractionFactRow
from services.supervisor_service.app import app as supervisor_app


def test_kb_lite_search():
    client = TestClient(kb_app)

    cat = client.post(
        "/knowledge/categories",
        json={"name": "NCA Layer", "description": "Certificate help"},
        headers={"X-User": "analyst", "X-Role": "analyst"},
    )
    assert cat.status_code == 200
    category_id = cat.json()["category_id"]

    article = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "title": "How to install NCA Layer",
            "body": "Download installer and run it.",
            "tags": ["nca", "install"],
        },
        headers={"X-User": "analyst", "X-Role": "analyst"},
    )
    assert article.status_code == 200

    search = client.get("/knowledge/search?q=NCA")
    assert search.status_code == 200
    assert len(search.json()) >= 1


def test_kb_lite_search_ranks_title_over_body_only_matches():
    client = TestClient(kb_app)
    headers = {"X-User": "analyst", "X-Role": "analyst"}

    cat = client.post(
        "/knowledge/categories",
        json={"name": "KB Rank", "description": "Ranking checks"},
        headers=headers,
    )
    assert cat.status_code == 200
    category_id = cat.json()["category_id"]

    top = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "title": "Rankedgex operator guide",
            "body": "Use this guide to connect a customer with an operator.",
            "tags": ["rankedgex", "operator"],
        },
        headers=headers,
    )
    assert top.status_code == 200

    fallback = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "title": "Fallback flow",
            "body": "This body mentions rankedgex operator once for fallback routing.",
            "tags": ["fallback"],
        },
        headers=headers,
    )
    assert fallback.status_code == 200

    search = client.get("/knowledge/search?q=rankedgex operator")
    assert search.status_code == 200
    payload = search.json()
    assert len(payload) >= 2
    assert payload[0]["title"] == "Rankedgex operator guide"


def test_kb_localized_articles_search_by_language_and_group():
    client = TestClient(kb_app)
    headers = {"X-User": "analyst", "X-Role": "analyst"}

    cat = client.post(
        "/knowledge/categories",
        json={"name": "Localized KB", "description": "RU/KZ localization"},
        headers=headers,
    )
    assert cat.status_code == 200
    category_id = cat.json()["category_id"]

    ru_article = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "language": "ru",
            "title": "Tariff localsharedx RU",
            "body": "Russian localized kb answer localsharedx.",
            "tags": ["localsharedx"],
        },
        headers=headers,
    )
    assert ru_article.status_code == 200
    article_group_id = ru_article.json()["article_group_id"]

    kz_article = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "article_group_id": article_group_id,
            "language": "kz",
            "title": "Tariff localsharedx KZ",
            "body": "Kazakh localized kb answer localsharedx.",
            "tags": ["localsharedx"],
        },
        headers=headers,
    )
    assert kz_article.status_code == 200
    assert kz_article.json()["article_group_id"] == article_group_id

    kz_search = client.get("/knowledge/search?q=localsharedx&language=kz")
    assert kz_search.status_code == 200
    kz_payload = kz_search.json()
    assert len(kz_payload) == 1
    assert kz_payload[0]["language"] == "kz"
    assert kz_payload[0]["article_group_id"] == article_group_id

    duplicate = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "article_group_id": article_group_id,
            "language": "kz",
            "title": "Tariff localsharedx KZ duplicate",
            "body": "Should be rejected.",
            "tags": ["localsharedx"],
        },
        headers=headers,
    )
    assert duplicate.status_code == 409


def test_kb_lite_search_empty_query_returns_articles_by_recency():
    client = TestClient(kb_app)
    headers = {"X-User": "analyst", "X-Role": "analyst"}

    cat = client.post(
        "/knowledge/categories",
        json={"name": "KB Empty Query", "description": "Empty query list view"},
        headers=headers,
    )
    assert cat.status_code == 200
    category_id = cat.json()["category_id"]

    older = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "title": "Empty Query Older",
            "body": "Older body",
            "tags": ["empty-query-older"],
        },
        headers=headers,
    )
    assert older.status_code == 200

    newer = client.post(
        "/knowledge/articles",
        json={
            "category_id": category_id,
            "title": "Empty Query Newer",
            "body": "Newer body",
            "tags": ["empty-query-newer"],
        },
        headers=headers,
    )
    assert newer.status_code == 200

    search = client.get("/knowledge/search?q=&limit=2")
    assert search.status_code == 200
    payload = search.json()
    assert [item["title"] for item in payload] == ["Empty Query Newer", "Empty Query Older"]


def test_kb_lite_role_enforcement():
    client = TestClient(kb_app)

    denied = client.post(
        "/knowledge/categories",
        json={"name": "Forbidden", "description": "Should be blocked"},
        headers={"X-User": "operator", "X-Role": "operator"},
    )
    assert denied.status_code == 403


def test_interaction_drilldown_filters_by_range_and_paginates():
    client = TestClient(interaction_app)
    session = get_session()
    marker = uuid4().hex[:8]
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_late",
                    channel="voice",
                    subject=f"Drilldown late {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_alpha",
                    priority=3,
                    status="new",
                    assigned_to="agent.late",
                    created_at="2026-02-03T12:00:00+00:00",
                    updated_at="2026-02-03T12:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_middle",
                    channel="voice",
                    subject=f"Drilldown middle {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_beta",
                    priority=3,
                    status="in_progress",
                    assigned_to="agent.middle",
                    created_at="2026-02-02T10:00:00+00:00",
                    updated_at="2026-02-02T10:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_early",
                    channel="telegram",
                    subject=f"Drilldown early {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_alpha",
                    priority=3,
                    status="closed",
                    assigned_to="agent.early",
                    created_at="2026-02-02T09:00:00+00:00",
                    updated_at="2026-02-02T09:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_outside",
                    channel="voice",
                    subject=f"Drilldown outside {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_alpha",
                    priority=3,
                    status="new",
                    assigned_to="agent.outside",
                    created_at="2026-02-05T09:00:00+00:00",
                    updated_at="2026-02-05T09:00:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    headers = {"X-User": "analyst", "X-Role": "analyst"}
    page_one = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-02T00:00:00Z",
            "to_ts": "2026-02-04T00:00:00Z",
            "limit": 2,
            "offset": 0,
        },
        headers=headers,
    )
    assert page_one.status_code == 200
    payload = page_one.json()
    assert payload["total"] >= 3
    assert payload["limit"] == 2
    assert payload["offset"] == 0
    assert payload["filters"]["from_ts"] == "2026-02-02T00:00:00+00:00"
    assert payload["filters"]["to_ts"] == "2026-02-04T00:00:00+00:00"
    assert [item["interaction_id"] for item in payload["items"][:2]] == [
        f"int_{marker}_late",
        f"int_{marker}_middle",
    ]

    page_two = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-02T00:00:00Z",
            "to_ts": "2026-02-04T00:00:00Z",
            "limit": 2,
            "offset": 2,
        },
        headers=headers,
    )
    assert page_two.status_code == 200
    second_payload = page_two.json()
    assert second_payload["offset"] == 2
    assert second_payload["items"][0]["interaction_id"] == f"int_{marker}_early"


def test_interaction_drilldown_filters_by_queue_and_channel():
    client = TestClient(interaction_app)
    session = get_session()
    marker = uuid4().hex[:8]
    queue_id = f"q_{marker}_focus"
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_target_one",
                    channel="telegram",
                    subject=f"Drilldown target one {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="new",
                    assigned_to="agent.target",
                    created_at="2026-02-10T08:00:00+00:00",
                    updated_at="2026-02-10T08:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_target_two",
                    channel="telegram",
                    subject=f"Drilldown target two {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to="agent.target",
                    created_at="2026-02-10T07:00:00+00:00",
                    updated_at="2026-02-10T07:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_queue_only",
                    channel="voice",
                    subject=f"Drilldown queue only {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="new",
                    assigned_to="agent.queue",
                    created_at="2026-02-10T06:00:00+00:00",
                    updated_at="2026-02-10T06:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_channel_only",
                    channel="telegram",
                    subject=f"Drilldown channel only {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_other",
                    priority=3,
                    status="new",
                    assigned_to="agent.channel",
                    created_at="2026-02-10T05:00:00+00:00",
                    updated_at="2026-02-10T05:00:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    headers = {"X-User": "analyst", "X-Role": "analyst"}
    queue_only = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-10T00:00:00Z",
            "to_ts": "2026-02-11T00:00:00Z",
            "queue_id": queue_id,
        },
        headers=headers,
    )
    assert queue_only.status_code == 200
    queue_payload = queue_only.json()
    assert queue_payload["filters"]["queue_id"] == queue_id
    assert {item["interaction_id"] for item in queue_payload["items"]} >= {
        f"int_{marker}_target_one",
        f"int_{marker}_target_two",
        f"int_{marker}_queue_only",
    }

    queue_and_channel = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-10T00:00:00Z",
            "to_ts": "2026-02-11T00:00:00Z",
            "queue_id": queue_id,
            "channel": "telegram",
        },
        headers=headers,
    )
    assert queue_and_channel.status_code == 200
    combined_payload = queue_and_channel.json()
    assert combined_payload["filters"]["queue_id"] == queue_id
    assert combined_payload["filters"]["channel"] == "telegram"
    assert [item["interaction_id"] for item in combined_payload["items"]] == [
        f"int_{marker}_target_one",
        f"int_{marker}_target_two",
    ]


def test_interaction_drilldown_search_status_and_sort_filters():
    client = TestClient(interaction_app)
    session = get_session()
    marker = uuid4().hex[:8]
    queue_id = f"q_{marker}_search"
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_subject_b",
                    channel="voice",
                    subject=f"Subject {marker} Bravo",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to=f"agent.{marker}.beta",
                    created_at="2026-02-12T09:00:00+00:00",
                    updated_at="2026-02-12T11:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_subject_a",
                    channel="telegram",
                    subject=f"Subject {marker} Alpha",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to=f"agent.{marker}.alpha",
                    created_at="2026-02-12T08:00:00+00:00",
                    updated_at="2026-02-12T12:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_assigned",
                    channel="webchat",
                    subject=f"General ticket {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="new",
                    assigned_to=f"agent.{marker}.search",
                    created_at="2026-02-12T07:00:00+00:00",
                    updated_at="2026-02-12T13:00:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    headers = {"X-User": "analyst", "X-Role": "analyst"}
    sorted_page = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-12T00:00:00Z",
            "to_ts": "2026-02-13T00:00:00Z",
            "q": marker,
            "status": "closed",
            "sort_by": "subject",
            "sort_dir": "asc",
            "limit": 1,
            "offset": 0,
        },
        headers=headers,
    )
    assert sorted_page.status_code == 200
    sorted_payload = sorted_page.json()
    assert sorted_payload["filters"]["q"] == marker
    assert sorted_payload["filters"]["status"] == "closed"
    assert sorted_payload["filters"]["sort_by"] == "subject"
    assert sorted_payload["filters"]["sort_dir"] == "asc"
    assert sorted_payload["total"] >= 2
    assert [item["interaction_id"] for item in sorted_payload["items"]] == [f"int_{marker}_subject_a"]

    second_page = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-12T00:00:00Z",
            "to_ts": "2026-02-13T00:00:00Z",
            "q": marker,
            "status": "closed",
            "sort_by": "subject",
            "sort_dir": "asc",
            "limit": 1,
            "offset": 1,
        },
        headers=headers,
    )
    assert second_page.status_code == 200
    second_payload = second_page.json()
    assert second_payload["offset"] == 1
    assert [item["interaction_id"] for item in second_payload["items"]] == [f"int_{marker}_subject_b"]


def test_interaction_drilldown_searches_interaction_id_and_assigned_to_and_sorts_by_updated_at():
    client = TestClient(interaction_app)
    session = get_session()
    marker = uuid4().hex[:8]
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_latest",
                    channel="voice",
                    subject=f"Updated latest {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_updated",
                    priority=3,
                    status="new",
                    assigned_to=f"agent.{marker}.team",
                    created_at="2026-02-14T08:00:00+00:00",
                    updated_at="2026-02-14T12:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_earlier",
                    channel="email",
                    subject=f"Updated earlier {marker}",
                    customer_id=None,
                    queue_id=f"q_{marker}_updated",
                    priority=3,
                    status="in_progress",
                    assigned_to=f"agent.{marker}.team",
                    created_at="2026-02-14T07:00:00+00:00",
                    updated_at="2026-02-14T11:00:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    headers = {"X-User": "analyst", "X-Role": "analyst"}
    id_lookup = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-14T00:00:00Z",
            "to_ts": "2026-02-15T00:00:00Z",
            "q": f"int_{marker}_latest",
        },
        headers=headers,
    )
    assert id_lookup.status_code == 200
    id_payload = id_lookup.json()
    assert [item["interaction_id"] for item in id_payload["items"]] == [f"int_{marker}_latest"]

    assigned_lookup = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-14T00:00:00Z",
            "to_ts": "2026-02-15T00:00:00Z",
            "q": f"agent.{marker}.team",
            "sort_by": "updated_at",
            "sort_dir": "desc",
        },
        headers=headers,
    )
    assert assigned_lookup.status_code == 200
    assigned_payload = assigned_lookup.json()
    assert assigned_payload["filters"]["sort_by"] == "updated_at"
    assert assigned_payload["filters"]["sort_dir"] == "desc"
    assert [item["interaction_id"] for item in assigned_payload["items"][:2]] == [
        f"int_{marker}_latest",
        f"int_{marker}_earlier",
    ]


def test_interaction_drilldown_filters_by_agent_id():
    client = TestClient(interaction_app)
    session = get_session()
    marker = uuid4().hex[:8]
    queue_id = f"q_{marker}_agent"
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_alpha",
                    channel="voice",
                    subject=f"Agent alpha {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to=f"agent.{marker}.focus",
                    created_at="2026-02-16T08:00:00+00:00",
                    updated_at="2026-02-16T09:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_beta",
                    channel="telegram",
                    subject=f"Agent beta {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="in_progress",
                    assigned_to=f"agent.{marker}.focus",
                    created_at="2026-02-16T07:00:00+00:00",
                    updated_at="2026-02-16T10:00:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_other",
                    channel="voice",
                    subject=f"Agent other {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to=f"agent.{marker}.other",
                    created_at="2026-02-16T06:00:00+00:00",
                    updated_at="2026-02-16T06:30:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    response = client.get(
        "/interactions/drilldown",
        params={
            "from_ts": "2026-02-16T00:00:00Z",
            "to_ts": "2026-02-17T00:00:00Z",
            "agent_id": f"agent.{marker}.focus",
        },
        headers=_analyst_headers(),
    )
    assert response.status_code == 200
    payload = response.json()
    assert payload["filters"]["agent_id"] == f"agent.{marker}.focus"
    assert [item["interaction_id"] for item in payload["items"]] == [
        f"int_{marker}_alpha",
        f"int_{marker}_beta",
    ]


def _admin_headers():
    return {"X-User": "admin", "X-Role": "admin"}


def _analyst_headers():
    return {"X-User": "analyst", "X-Role": "analyst"}


def _post_reporting_fact(client: TestClient, **payload):
    response = client.post("/reports/facts/interactions", json=payload, headers=_admin_headers())
    assert response.status_code == 200
    return response


def test_reporting_interaction_fact_upsert_is_idempotent():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    interaction_id = f"rpt_fact_{marker}"
    queue_id = f"q_fact_{marker}"

    first = _post_reporting_fact(
        client,
        interaction_id=interaction_id,
        channel="voice",
        queue_id=queue_id,
        status="new",
        created_at="2026-03-02T08:00:00Z",
        source="test-suite",
    )
    second = _post_reporting_fact(
        client,
        interaction_id=interaction_id,
        channel="voice",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-02T08:00:00Z",
        answered=True,
        wait_seconds=15,
        handle_seconds=90,
        resolved_first_contact=True,
        source="test-suite",
    )

    assert first.json()["interaction_id"] == interaction_id
    assert second.json()["interaction_id"] == interaction_id

    session = get_session()
    try:
        rows = session.execute(
            select(ReportingInteractionFactRow).where(
                ReportingInteractionFactRow.interaction_id == interaction_id
            )
        ).scalars().all()
        assert len(rows) == 1
        row = rows[0]
        assert row.status == "closed"
        assert row.answered is True
        assert row.wait_seconds == 15
        assert row.handle_seconds == 90
        assert row.resolved_first_contact is True
    finally:
        session.close()


def test_reporting_kpi_uses_exact_voice_fact_layer_and_exposes_coverage():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_stage3_kpi_{marker}"
    window = {
        "from_ts": "2026-03-03T00:00:00Z",
        "to_ts": "2026-03-04T00:00:00Z",
    }

    rows = [
        {
            "interaction_id": f"int_{marker}_one",
            "channel": "voice",
            "queue_id": queue_id,
            "status": "closed",
            "created_at": "2026-03-03T08:00:00Z",
            "closed_at": "2026-03-03T08:10:00Z",
            "answered": True,
            "wait_seconds": 20,
            "handle_seconds": 100,
            "abandoned": False,
            "resolved_first_contact": True,
            "source": "test-suite",
        },
        {
            "interaction_id": f"int_{marker}_two",
            "channel": "voice",
            "queue_id": queue_id,
            "status": "closed",
            "created_at": "2026-03-03T09:00:00Z",
            "closed_at": "2026-03-03T09:15:00Z",
            "answered": True,
            "wait_seconds": 40,
            "handle_seconds": 120,
            "abandoned": False,
            "resolved_first_contact": False,
            "source": "test-suite",
        },
        {
            "interaction_id": f"int_{marker}_three",
            "channel": "voice",
            "queue_id": queue_id,
            "status": "abandoned",
            "created_at": "2026-03-03T10:00:00Z",
            "closed_at": "2026-03-03T10:03:00Z",
            "answered": False,
            "wait_seconds": 10,
            "handle_seconds": 0,
            "abandoned": True,
            "resolved_first_contact": False,
            "source": "test-suite",
        },
    ]

    for row in rows:
        _post_reporting_fact(client, **row)

    kpi = client.get(
        "/reports/kpi",
        params={**window, "queue_id": queue_id, "sl_threshold_seconds": 30},
    )
    assert kpi.status_code == 200
    body = kpi.json()
    assert body["volume"]["total"] == 3
    assert body["volume"]["answered"] == 2
    assert body["volume"]["abandoned"] == 1
    assert body["kpi"]["SL"] == pytest.approx(33.33, rel=0, abs=0.01)
    assert body["kpi"]["ASA"] == pytest.approx(30.0, rel=0, abs=0.01)
    assert body["kpi"]["AHT"] == pytest.approx(110.0, rel=0, abs=0.01)
    assert body["kpi"]["Abandon"] == pytest.approx(33.33, rel=0, abs=0.01)
    assert body["kpi"]["FCR"] == pytest.approx(50.0, rel=0, abs=0.01)
    assert body["kpi"]["AnswerRate"] == pytest.approx(66.67, rel=0, abs=0.01)
    assert body["kpi"]["DigitalShare"] == pytest.approx(0.0, rel=0, abs=0.01)
    assert body["breakdowns"]["by_channel"]["voice"]["total"] == 3
    assert body["coverage"]["metric_status"]["total"] == "exact"
    assert body["coverage"]["metric_status"]["answered"] == "exact"
    assert body["coverage"]["metric_status"]["SL"] == "exact"
    assert body["coverage"]["metric_status"]["ASA"] == "exact"
    assert body["coverage"]["metric_status"]["AHT"] == "exact"
    assert body["coverage"]["metric_status"]["Abandon"] == "exact"
    assert body["coverage"]["metric_status"]["FCR"] == "exact"
    assert body["coverage"]["metric_status"]["DigitalShare"] == "exact"
    assert body["coverage"]["supported_channels"]["SL"] == ["voice"]
    assert body["coverage"]["exact_rows"] == 3
    assert body["coverage"]["total_rows"] == 3

    coverage = client.get("/reports/coverage")
    assert coverage.status_code == 200
    coverage_payload = coverage.json()
    metric_codes = {item["code"] for item in coverage_payload["implemented_metrics"]}
    assert "SL" in metric_codes
    assert "AnswerRate" in metric_codes
    assert "DigitalShare" in metric_codes


def test_reporting_drilldown_returns_exact_metric_slices_and_fact_fields():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_stage3_drilldown_{marker}"
    session = get_session()
    try:
        session.add_all(
            [
                Interaction(
                    interaction_id=f"int_{marker}_one",
                    channel="voice",
                    subject=f"Voice exact one {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to="agent.alpha",
                    created_at="2026-03-04T08:00:00+00:00",
                    updated_at="2026-03-04T08:10:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_two",
                    channel="voice",
                    subject=f"Voice exact two {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="closed",
                    assigned_to="agent.beta",
                    created_at="2026-03-04T09:00:00+00:00",
                    updated_at="2026-03-04T09:15:00+00:00",
                ),
                Interaction(
                    interaction_id=f"int_{marker}_three",
                    channel="voice",
                    subject=f"Voice abandoned {marker}",
                    customer_id=None,
                    queue_id=queue_id,
                    priority=3,
                    status="abandoned",
                    assigned_to=None,
                    created_at="2026-03-04T10:00:00+00:00",
                    updated_at="2026-03-04T10:03:00+00:00",
                ),
            ]
        )
        session.commit()
    finally:
        session.close()

    for row in [
        {
            "interaction_id": f"int_{marker}_one",
            "channel": "voice",
            "queue_id": queue_id,
            "agent_id": "agent.alpha",
            "status": "closed",
            "created_at": "2026-03-04T08:00:00Z",
            "closed_at": "2026-03-04T08:10:00Z",
            "answered": True,
            "wait_seconds": 20,
            "handle_seconds": 100,
            "abandoned": False,
            "resolved_first_contact": True,
            "source": "test-suite",
        },
        {
            "interaction_id": f"int_{marker}_two",
            "channel": "voice",
            "queue_id": queue_id,
            "agent_id": "agent.beta",
            "status": "closed",
            "created_at": "2026-03-04T09:00:00Z",
            "closed_at": "2026-03-04T09:15:00Z",
            "answered": True,
            "wait_seconds": 40,
            "handle_seconds": 120,
            "abandoned": False,
            "resolved_first_contact": False,
            "source": "test-suite",
        },
        {
            "interaction_id": f"int_{marker}_three",
            "channel": "voice",
            "queue_id": queue_id,
            "status": "abandoned",
            "created_at": "2026-03-04T10:00:00Z",
            "closed_at": "2026-03-04T10:03:00Z",
            "answered": False,
            "wait_seconds": 10,
            "handle_seconds": 0,
            "abandoned": True,
            "resolved_first_contact": False,
            "source": "test-suite",
        },
    ]:
        _post_reporting_fact(client, **row)

    base_params = {
        "from_ts": "2026-03-04T00:00:00Z",
        "to_ts": "2026-03-05T00:00:00Z",
        "queue_id": queue_id,
        "sl_threshold_seconds": 30,
    }

    sl_slice = client.get("/reports/drilldown", params={**base_params, "metric": "SL"})
    assert sl_slice.status_code == 200
    sl_payload = sl_slice.json()
    assert sl_payload["metric"] == "SL"
    assert sl_payload["coverage"]["status"] == "exact"
    assert sl_payload["total"] == 1
    assert [item["interaction_id"] for item in sl_payload["items"]] == [f"int_{marker}_one"]
    assert sl_payload["items"][0]["within_sla"] is True
    assert sl_payload["items"][0]["wait_seconds"] == 20
    assert sl_payload["items"][0]["handle_seconds"] == 100
    assert sl_payload["items"][0]["resolved_first_contact"] is True

    aht_slice = client.get("/reports/drilldown", params={**base_params, "metric": "AHT"})
    assert aht_slice.status_code == 200
    aht_payload = aht_slice.json()
    assert aht_payload["total"] == 2
    assert [item["interaction_id"] for item in aht_payload["items"][:2]] == [
        f"int_{marker}_two",
        f"int_{marker}_one",
    ]
    assert aht_payload["items"][0]["handle_seconds"] == 120

    abandon_slice = client.get("/reports/drilldown", params={**base_params, "metric": "Abandon"})
    assert abandon_slice.status_code == 200
    abandon_payload = abandon_slice.json()
    assert abandon_payload["total"] == 1
    assert abandon_payload["items"][0]["interaction_id"] == f"int_{marker}_three"
    assert abandon_payload["items"][0]["abandoned"] is True

    fcr_slice = client.get("/reports/drilldown", params={**base_params, "metric": "FCR"})
    assert fcr_slice.status_code == 200
    fcr_payload = fcr_slice.json()
    assert fcr_payload["total"] == 1
    assert fcr_payload["items"][0]["interaction_id"] == f"int_{marker}_one"
    assert fcr_payload["items"][0]["resolved_first_contact"] is True


def test_reporting_kpi_coverage_is_partial_for_mixed_voice_and_digital_windows():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_stage3_mixed_{marker}"
    window = {
        "from_ts": "2026-03-05T00:00:00Z",
        "to_ts": "2026-03-06T00:00:00Z",
    }

    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_voice",
        channel="voice",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-05T08:00:00Z",
        closed_at="2026-03-05T08:10:00Z",
        answered=True,
        wait_seconds=18,
        handle_seconds=80,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_telegram",
        channel="telegram",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-05T09:00:00Z",
        closed_at="2026-03-05T09:05:00Z",
        source="test-suite",
    )

    kpi = client.get("/reports/kpi", params={**window, "queue_id": queue_id, "sl_threshold_seconds": 30})
    assert kpi.status_code == 200
    payload = kpi.json()
    assert payload["volume"]["total"] == 2
    assert payload["kpi"]["DigitalShare"] == pytest.approx(50.0, rel=0, abs=0.01)
    assert payload["coverage"]["metric_status"]["total"] == "exact"
    assert payload["coverage"]["metric_status"]["DigitalShare"] == "exact"
    assert payload["coverage"]["metric_status"]["SL"] == "partial"
    assert payload["coverage"]["metric_status"]["ASA"] == "partial"
    assert payload["coverage"]["metric_status"]["FCR"] == "partial"

    asa_slice = client.get("/reports/drilldown", params={**window, "queue_id": queue_id, "metric": "ASA"})
    assert asa_slice.status_code == 200
    asa_payload = asa_slice.json()
    assert asa_payload["coverage"]["status"] == "partial"
    assert asa_payload["total"] == 1
    assert asa_payload["items"][0]["interaction_id"] == f"int_{marker}_voice"


def test_interaction_status_close_with_resolved_first_contact_updates_reporting_fact():
    interaction_client = TestClient(interaction_app)
    reporting_client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    headers = {"X-User": "operator", "X-Role": "operator"}

    created = interaction_client.post(
        "/interactions",
        json={
            "channel": "voice",
            "subject": f"Resolved FCR {marker}",
            "queue_id": f"q_interaction_{marker}",
            "priority": 3,
        },
        headers=headers,
    )
    assert created.status_code == 200
    interaction = created.json()
    interaction_id = interaction["interaction_id"]

    _post_reporting_fact(
        reporting_client,
        interaction_id=interaction_id,
        channel="voice",
        queue_id=interaction["queue_id"],
        status="in_progress",
        created_at=interaction["created_at"],
        answered=True,
        wait_seconds=14,
        handle_seconds=95,
        resolved_first_contact=False,
        source="test-suite",
    )

    closed = interaction_client.patch(
        f"/interactions/{interaction_id}/status",
        json={"status": "closed", "resolved_first_contact": True},
        headers=headers,
    )
    assert closed.status_code == 200
    assert closed.json()["status"] == "closed"

    session = get_session()
    try:
        row = session.execute(
            select(ReportingInteractionFactRow).where(
                ReportingInteractionFactRow.interaction_id == interaction_id
            )
        ).scalar_one()
        assert row.status == "closed"
        assert row.resolved_first_contact is True
        assert row.answered is True
        assert row.handle_seconds == 95
        assert row.closed_at is not None
    finally:
        session.close()

    fcr_slice = reporting_client.get(
        "/reports/drilldown",
        params={
            "from_ts": "2026-01-01T00:00:00Z",
            "to_ts": "2027-01-01T00:00:00Z",
            "queue_id": interaction["queue_id"],
            "channel": "voice",
            "metric": "FCR",
        },
    )
    assert fcr_slice.status_code == 200
    fcr_payload = fcr_slice.json()
    assert interaction_id in {item["interaction_id"] for item in fcr_payload["items"]}


def test_reporting_export_csv():
    client = TestClient(reporting_app)

    ingested = client.post(
        "/reports/events",
        json={
            "queue_id": "q_stage3_csv",
            "answered": True,
            "wait_seconds": 12,
            "handle_seconds": 90,
            "abandoned": False,
            "resolved_first_contact": True,
        },
    )
    assert ingested.status_code == 200

    exported = client.get("/reports/export")
    assert exported.status_code == 200
    assert exported.headers.get("content-type", "").startswith("text/csv")
    assert (
        "queue_id,channel,agent_id,answered,wait_seconds,handle_seconds,abandoned,resolved_first_contact,created_at"
        in exported.text
    )
    assert "q_stage3_csv" in exported.text


def test_reporting_saved_views_are_server_side_per_user():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]

    created = client.post(
        "/reports/views",
        headers=_analyst_headers(),
        json={
            "name": f"Telegram {marker}",
            "snapshot": {
                "preset": "30d",
                "channel": "telegram",
                "queueId": "all",
                "compareMode": "previous",
                "trendMetric": "volume",
                "aiTrendMetric": "containment_rate",
            },
        },
    )
    assert created.status_code == 200
    payload = created.json()
    assert payload["name"] == f"Telegram {marker}"
    assert payload["snapshot"]["channel"] == "telegram"

    listed = client.get("/reports/views", headers=_analyst_headers())
    assert listed.status_code == 200
    listed_payload = listed.json()
    assert payload["id"] in {item["id"] for item in listed_payload}

    other_user = client.get("/reports/views", headers={"X-User": f"analyst_{marker}", "X-Role": "analyst"})
    assert other_user.status_code == 200
    assert payload["id"] not in {item["id"] for item in other_user.json()}

    deleted = client.delete(f"/reports/views/{payload['id']}", headers=_analyst_headers())
    assert deleted.status_code == 200
    assert deleted.json()["accepted"] is True


def test_reporting_timeseries_uses_exact_fact_layer():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_timeseries_{marker}"
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_day1",
        channel="voice",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-10T08:00:00Z",
        closed_at="2026-03-10T08:10:00Z",
        answered=True,
        wait_seconds=20,
        handle_seconds=100,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_day2",
        channel="voice",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-11T08:00:00Z",
        closed_at="2026-03-11T08:10:00Z",
        answered=True,
        wait_seconds=45,
        handle_seconds=80,
        abandoned=False,
        resolved_first_contact=False,
        source="test-suite",
    )

    response = client.get(
        "/reports/timeseries",
        params={
            "from_ts": "2026-03-10T00:00:00Z",
            "to_ts": "2026-03-12T00:00:00Z",
            "queue_id": queue_id,
            "metric": "SL",
            "interval": "day",
            "sl_threshold_seconds": 30,
        },
    )
    assert response.status_code == 200
    payload = response.json()
    assert payload["metric"] == "SL"
    assert payload["interval"] == "day"
    assert [point["value"] for point in payload["points"]] == [100.0, 0.0]
    assert [point["sample_size"] for point in payload["points"]] == [1, 1]


def test_reporting_agent_overview_aggregates_facts_and_current_states():
    client = TestClient(reporting_app)
    supervisor_client = TestClient(supervisor_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_agent_overview_{marker}"

    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_one",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.alpha",
        status="closed",
        created_at="2026-03-18T08:00:00Z",
        closed_at="2026-03-18T08:10:00Z",
        answered=True,
        wait_seconds=12,
        handle_seconds=75,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_two",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.alpha",
        status="in_progress",
        created_at="2026-03-18T09:00:00Z",
        answered=True,
        wait_seconds=20,
        handle_seconds=90,
        abandoned=False,
        resolved_first_contact=False,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_three",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.beta",
        status="closed",
        created_at="2026-03-18T10:00:00Z",
        closed_at="2026-03-18T10:05:00Z",
        answered=False,
        wait_seconds=25,
        handle_seconds=0,
        abandoned=True,
        resolved_first_contact=False,
        source="test-suite",
    )

    state_alpha = supervisor_client.post(
        "/supervisor/agent-states",
        headers=_admin_headers(),
        json={"agent_id": f"agent.{marker}.alpha", "state": "READY", "queue_id": queue_id},
    )
    assert state_alpha.status_code == 200
    state_gamma = supervisor_client.post(
        "/supervisor/agent-states",
        headers=_admin_headers(),
        json={"agent_id": f"agent.{marker}.gamma", "state": "BREAK", "queue_id": queue_id},
    )
    assert state_gamma.status_code == 200

    response = client.get(
        "/reports/agents/overview",
        params={
            "from_ts": "2026-03-18T00:00:00Z",
            "to_ts": "2026-03-19T00:00:00Z",
            "queue_id": queue_id,
        },
    )
    assert response.status_code == 200
    payload = response.json()
    assert payload["totals"]["agents_total"] >= 3
    assert payload["totals"]["agents_with_activity"] == 2
    assert payload["totals"]["interactions_total"] == 3
    assert payload["totals"]["answered_total"] == 2
    assert payload["totals"]["ready_now"] >= 1
    assert payload["totals"]["break_now"] >= 1
    assert payload["filters"]["sort_by"] == "interactions_total"
    assert payload["filters"]["sort_dir"] == "desc"
    assert payload["items"][0]["agent_id"] == f"agent.{marker}.alpha"
    assert payload["items"][0]["interactions_total"] == 2
    assert payload["items"][0]["avg_handle_seconds"] == pytest.approx(82.5, rel=0, abs=0.01)
    assert payload["items"][0]["fcr_rate"] == pytest.approx(50.0, rel=0, abs=0.01)
    assert payload["state_snapshot"]["by_state"]["READY"] >= 1
    assert payload["breakdowns"]["by_team"][0]["team_key"] == queue_id
    assert payload["breakdowns"]["by_team"][0]["interactions_total"] == 3
    day_shift = next(item for item in payload["breakdowns"]["by_shift"] if item["shift_key"] == "day")
    assert day_shift["interactions_total"] == 3
    assert day_shift["answered_total"] == 2


def test_reporting_agent_timeseries_returns_metric_points():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_agent_ts_{marker}"

    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_one",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.alpha",
        status="closed",
        created_at="2026-03-20T08:00:00Z",
        closed_at="2026-03-20T08:06:00Z",
        answered=True,
        wait_seconds=10,
        handle_seconds=60,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_two",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.beta",
        status="closed",
        created_at="2026-03-20T09:00:00Z",
        closed_at="2026-03-20T09:08:00Z",
        answered=True,
        wait_seconds=14,
        handle_seconds=120,
        abandoned=False,
        resolved_first_contact=False,
        source="test-suite",
    )
    _post_reporting_fact(
        client,
        interaction_id=f"int_{marker}_three",
        channel="voice",
        queue_id=queue_id,
        agent_id=f"agent.{marker}.alpha",
        status="closed",
        created_at="2026-03-21T11:00:00Z",
        closed_at="2026-03-21T11:07:00Z",
        answered=True,
        wait_seconds=11,
        handle_seconds=90,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )

    response = client.get(
        "/reports/agents/timeseries",
        params={
            "from_ts": "2026-03-20T00:00:00Z",
            "to_ts": "2026-03-22T00:00:00Z",
            "queue_id": queue_id,
            "metric": "interactions_per_agent",
            "interval": "day",
        },
    )
    assert response.status_code == 200
    payload = response.json()
    assert payload["metric"] == "interactions_per_agent"
    assert payload["interval"] == "day"
    assert payload["filters"]["queue_id"] == queue_id
    assert [point["interactions_total"] for point in payload["points"]] == [2, 1]
    assert [point["agents_with_activity"] for point in payload["points"]] == [2, 1]
    assert [point["value"] for point in payload["points"]] == [1.0, 1.0]


def test_reporting_export_exact_csv_from_fact_window():
    client = TestClient(reporting_app)
    marker = uuid4().hex[:8]
    queue_id = f"q_export_exact_{marker}"
    interaction_id = f"int_{marker}_export"
    _post_reporting_fact(
        client,
        interaction_id=interaction_id,
        channel="voice",
        queue_id=queue_id,
        status="closed",
        created_at="2026-03-15T08:00:00Z",
        closed_at="2026-03-15T08:05:00Z",
        answered=True,
        wait_seconds=12,
        handle_seconds=55,
        abandoned=False,
        resolved_first_contact=True,
        source="test-suite",
    )

    exported = client.get(
        "/reports/export",
        params={
            "from_ts": "2026-03-15T00:00:00Z",
            "to_ts": "2026-03-16T00:00:00Z",
            "queue_id": queue_id,
            "metric": "FCR",
        },
    )
    assert exported.status_code == 200
    assert exported.headers.get("content-type", "").startswith("text/csv")
    assert "interaction_id,subject,channel,status,queue_id,assigned_to,created_at,updated_at,answered,abandoned,wait_seconds,handle_seconds,within_sla,resolved_first_contact" in exported.text
    assert interaction_id in exported.text


def test_supervisor_realtime():
    client = TestClient(supervisor_app)
    supervisor_headers = {"X-User": "supervisor", "X-Role": "supervisor"}
    analyst_headers = {"X-User": "analyst", "X-Role": "analyst"}

    up1 = client.post(
        "/supervisor/agent-states",
        json={"agent_id": "a1", "state": "READY", "queue_id": "q1"},
        headers=supervisor_headers,
    )
    up2 = client.post(
        "/supervisor/agent-states",
        json={"agent_id": "a2", "state": "BUSY", "queue_id": "q1"},
        headers=supervisor_headers,
    )
    assert up1.status_code == 200
    assert up2.status_code == 200

    queue = client.post(
        "/supervisor/queue-metrics",
        params={"queue_id": "q1", "in_queue": 2, "avg_wait_seconds": 18},
        headers=supervisor_headers,
    )
    assert queue.status_code == 200

    rt = client.get("/supervisor/realtime", headers=analyst_headers)
    assert rt.status_code == 200
    assert rt.json()["agents"]["total"] >= 2
    assert len(rt.json()["queues"]) >= 1


def test_supervisor_role_enforcement():
    client = TestClient(supervisor_app)

    denied = client.post(
        "/supervisor/agent-states",
        json={"agent_id": "op-1", "state": "READY", "queue_id": "q1"},
        headers={"X-User": "operator", "X-Role": "operator"},
    )
    assert denied.status_code == 403
