"""Tests for revision-gated /state/summary caching."""
from __future__ import annotations

import pytest

from api.services.summary_cache import (
    SummaryCache,
    SummaryRevision,
    fetch_summary_revision,
    get_summary_cache,
    invalidate_summary_cache,
    reset_summary_cache_for_tests,
)
from tests.test_routers_core import _create_domain, _create_task, _create_topic, _create_workstream


@pytest.mark.asyncio
async def test_fetch_summary_revision_empty_db(test_engine):
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker

    factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        revision = await fetch_summary_revision(session)
        assert revision.core_fingerprint()
        assert revision.progress_fingerprint() == ""


@pytest.mark.asyncio
async def test_summary_hit_revision_header(client):
    r1 = await client.get("/state/summary")
    assert r1.status_code == 200
    assert r1.headers.get("X-StateHub-Cache") == "miss"
    assert r1.headers.get("X-StateHub-Revision")

    r2 = await client.get("/state/summary")
    assert r2.status_code == 200
    assert r2.headers.get("X-StateHub-Cache") == "hit-revision"
    assert r2.headers.get("X-StateHub-Revision") == r1.headers.get("X-StateHub-Revision")


@pytest.mark.asyncio
async def test_summary_force_refresh_query(client):
    await client.get("/state/summary")
    r = await client.get("/state/summary", params={"refresh": "true"})
    assert r.status_code == 200
    assert r.headers.get("X-StateHub-Cache") == "miss"


@pytest.mark.asyncio
async def test_summary_no_cache_header(client):
    await client.get("/state/summary")
    r = await client.get("/state/summary", headers={"Cache-Control": "no-cache"})
    assert r.status_code == 200
    assert r.headers.get("X-StateHub-Cache") == "miss"


@pytest.mark.asyncio
async def test_summary_stale_while_revalidate(client, monkeypatch):
    await client.get("/state/summary")

    original = fetch_summary_revision

    async def bumped_revision(session):
        rev = await original(session)
        from datetime import datetime, timedelta, timezone

        return SummaryRevision(
            core=rev.core + timedelta(seconds=1),
            progress=rev.progress,
            sbom=rev.sbom,
        )

    monkeypatch.setattr("api.routers.state.fetch_summary_revision", bumped_revision)

    r = await client.get("/state/summary")
    assert r.status_code == 200
    assert r.headers.get("X-StateHub-Cache") == "stale"


@pytest.mark.asyncio
async def test_progress_section_refresh_without_full_rebuild(client):
    await _create_domain(client)
    topic = await _create_topic(client)
    await client.get("/state/summary")
    before = (await client.get("/state/summary")).headers.get("X-StateHub-Revision")

    await client.post(
        "/progress/",
        json={
            "topic_id": topic["id"],
            "event_type": "note",
            "summary": "cache section test",
            "author": "pytest",
        },
    )

    r = await client.get("/state/summary")
    assert r.status_code == 200
    assert r.headers.get("X-StateHub-Cache") == "hit-revision"
    assert r.json()["recent_progress"][0]["summary"] == "cache section test"
    assert r.headers.get("X-StateHub-Revision") != before


@pytest.mark.asyncio
async def test_task_mutation_invalidates_core_cache(client):
    await _create_domain(client)
    topic = await _create_topic(client)
    ws = await _create_workstream(client, topic_id=topic["id"])
    task = await _create_task(client, ws["id"])

    await client.get("/state/summary")
    assert (await client.get("/state/summary")).headers.get("X-StateHub-Cache") == "hit-revision"

    await client.patch(f"/tasks/{task['id']}", json={"status": "progress"})

    r = await client.get("/state/summary")
    assert r.status_code == 200
    assert r.headers.get("X-StateHub-Cache") == "miss"


def test_summary_cache_unit_progress_section():
    cache = SummaryCache()
    from datetime import datetime, timezone

    from api.schemas.state import StateSummary, Totals, TopicTotals, WorkstreamTotals, TaskTotals, DecisionTotals

    rev = SummaryRevision(
        core=datetime(2026, 1, 1, tzinfo=timezone.utc),
        progress=datetime(2026, 1, 2, tzinfo=timezone.utc),
        sbom=None,
    )
    empty_totals = Totals(
        topics=TopicTotals(),
        workstreams=WorkstreamTotals(),
        tasks=TaskTotals(),
        decisions=DecisionTotals(),
    )
    summary = StateSummary(
        generated_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
        totals=empty_totals,
        topics=[],
        blocking_decisions=[],
        waiting_tasks=[],
        recent_progress=[],
        open_workstreams=[],
    )
    cache.store(summary, rev)

    new_rev = SummaryRevision(
        core=rev.core,
        progress=datetime(2026, 1, 3, tzinfo=timezone.utc),
        sbom=None,
    )
    status, cached = cache.resolve(new_rev, force_refresh=False)
    assert status == "progress-section"
    assert cached is summary


def test_invalidate_summary_cache_scopes():
    reset_summary_cache_for_tests()
    cache = get_summary_cache()
    from datetime import datetime, timezone

    from api.schemas.state import StateSummary, Totals, TopicTotals, WorkstreamTotals, TaskTotals, DecisionTotals

    rev = SummaryRevision(
        core=datetime(2026, 1, 1, tzinfo=timezone.utc),
        progress=None,
        sbom=None,
    )
    empty_totals = Totals(
        topics=TopicTotals(),
        workstreams=WorkstreamTotals(),
        tasks=TaskTotals(),
        decisions=DecisionTotals(),
    )
    summary = StateSummary(
        generated_at=datetime(2026, 1, 1, tzinfo=timezone.utc),
        totals=empty_totals,
        topics=[],
        blocking_decisions=[],
        waiting_tasks=[],
        recent_progress=[],
        open_workstreams=[],
    )
    cache.store(summary, rev)

    invalidate_summary_cache("progress")
    status, _ = cache.resolve(rev, force_refresh=False)
    assert status == "progress-section"

    invalidate_summary_cache("core")
    status, cached = cache.resolve(rev, force_refresh=False)
    assert status == "miss"
    assert cached is None