"""get_knowledge_counts — one number per facet, and an honest total.

Two grouped queries, one per typing axis. It used to be three: a grouped count
over note_type restricted to ("note", "process"), a scalar count of tasks, and
a second scalar just for plans. That shape is why `issue` had no number —
every kind needed a query of its own and nobody added one — and why the "All"
chip sat ~90 below the list it labelled, since snippets were in the feed but
in no count (#3128).
"""
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from tests.helpers import make_mock_session


def _grouped(rows):
    r = MagicMock()
    r.all.return_value = rows
    return r


async def _counts(non_task_rows, kind_rows, **kwargs):
    session = make_mock_session()
    # 1) non-task rows grouped by note_type, 2) task rows grouped by task_kind
    session.execute = AsyncMock(
        side_effect=[_grouped(non_task_rows), _grouped(kind_rows)]
    )
    with patch("scribe.services.knowledge.async_session") as cls:
        cls.return_value = session
        from scribe.services.knowledge import get_knowledge_counts
        return await get_knowledge_counts(user_id=1, **kwargs), session


@pytest.mark.asyncio
async def test_every_facet_gets_a_number_including_the_kinds():
    counts, _ = await _counts(
        [("note", 395), ("process", 3), ("snippet", 90)],
        [("work", 2104), ("issue", 435), ("spike", 1), ("plan", 90)],
    )
    assert counts["note"] == 395
    assert counts["process"] == 3
    assert counts["snippet"] == 90
    assert counts["issue"] == 435
    assert counts["spike"] == 1
    assert counts["plan"] == 90
    assert counts["work"] == 2104


@pytest.mark.asyncio
async def test_task_is_the_sum_of_its_kinds():
    """`task` is not counted separately any more — it is what the kinds add up
    to, so the two can't disagree."""
    counts, _ = await _counts([], [("work", 2104), ("issue", 435), ("spike", 1)])
    assert counts["task"] == 2540


@pytest.mark.asyncio
async def test_total_counts_snippets_and_counts_no_task_twice():
    """The All chip labels a feed that contains every kind, so it has to count
    every kind — and exactly once. Kinds are subsets of `task`; adding them
    would count each issue a second time."""
    counts, _ = await _counts(
        [("note", 10), ("process", 2), ("snippet", 5)],
        [("work", 20), ("issue", 4)],
    )
    assert counts["task"] == 24
    assert counts["total"] == 10 + 2 + 5 + 24


@pytest.mark.asyncio
async def test_absent_facets_report_zero_rather_than_missing():
    counts, _ = await _counts([("note", 1)], [])
    assert counts["note"] == 1
    for key in ("process", "snippet", "task", "work", "issue", "spike", "plan"):
        assert counts[key] == 0, key
    assert counts["total"] == 1


@pytest.mark.asyncio
async def test_a_tag_filter_narrows_both_axes():
    """A tag has to reach both queries, or the chips would disagree with each
    other under a filter — tasks narrowed, notes not."""
    _, session = await _counts([], [], tags=["python"])
    assert session.execute.await_count == 2
    for call in session.execute.await_args_list:
        assert "notes.tags" in str(call.args[0])
