"""Tests for DegradationContext and contextvar lifecycle."""

import asyncio

import pytest

from infrastructure.degradation import (
    DegradationContext,
    clear_degradation_context,
    get_degradation_context,
    init_degradation_context,
    try_get_degradation_context,
)
from infrastructure.integration_result import IntegrationResult




class TestDegradationContext:
    def test_empty_context(self):
        ctx = DegradationContext()
        assert ctx.summary() == {}
        assert ctx.has_degradation() is False
        assert ctx.degraded_summary() == {}

    def test_record_ok(self):
        ctx = DegradationContext()
        ctx.record(IntegrationResult.ok(data=[1], source="musicbrainz"))
        assert ctx.summary() == {"musicbrainz": "ok"}
        assert ctx.has_degradation() is False

    def test_record_error(self):
        ctx = DegradationContext()
        ctx.record(IntegrationResult.error(source="jellyfin", msg="timeout"))
        assert ctx.summary() == {"jellyfin": "error"}
        assert ctx.has_degradation() is True
        assert ctx.degraded_summary() == {"jellyfin": "error"}

    def test_record_degraded(self):
        ctx = DegradationContext()
        ctx.record(
            IntegrationResult.degraded(data=[], source="audiodb", msg="rate limit")
        )
        assert ctx.summary() == {"audiodb": "degraded"}
        assert ctx.has_degradation() is True

    def test_worst_status_wins(self):
        ctx = DegradationContext()
        ctx.record(IntegrationResult.ok(data=[], source="musicbrainz"))
        ctx.record(IntegrationResult.degraded(data=[], source="musicbrainz", msg="slow"))
        assert ctx.summary() == {"musicbrainz": "degraded"}

    def test_error_beats_degraded(self):
        ctx = DegradationContext()
        ctx.record(
            IntegrationResult.degraded(data=[], source="musicbrainz", msg="slow")
        )
        ctx.record(IntegrationResult.error(source="musicbrainz", msg="503"))
        assert ctx.summary() == {"musicbrainz": "error"}

    def test_cannot_downgrade(self):
        ctx = DegradationContext()
        ctx.record(IntegrationResult.error(source="jellyfin", msg="down"))
        ctx.record(IntegrationResult.ok(data=[1], source="jellyfin"))
        assert ctx.summary() == {"jellyfin": "error"}

    def test_multiple_sources(self):
        ctx = DegradationContext()
        ctx.record(IntegrationResult.ok(data=[], source="musicbrainz"))
        ctx.record(IntegrationResult.error(source="jellyfin", msg="down"))
        ctx.record(
            IntegrationResult.degraded(data={}, source="audiodb", msg="slow")
        )
        assert ctx.summary() == {
            "musicbrainz": "ok",
            "jellyfin": "error",
            "audiodb": "degraded",
        }
        assert ctx.degraded_summary() == {
            "jellyfin": "error",
            "audiodb": "degraded",
        }




class TestContextVarLifecycle:
    def test_no_context_raises(self):
        clear_degradation_context()
        with pytest.raises(RuntimeError, match="outside a request scope"):
            get_degradation_context()

    def test_try_get_returns_none_outside(self):
        clear_degradation_context()
        assert try_get_degradation_context() is None

    def test_init_and_get(self):
        ctx = init_degradation_context()
        assert get_degradation_context() is ctx
        clear_degradation_context()

    def test_clear_removes_context(self):
        init_degradation_context()
        clear_degradation_context()
        assert try_get_degradation_context() is None

    @pytest.mark.asyncio
    async def test_isolated_across_tasks(self):
        """Context in one asyncio task must not leak into another."""
        results: dict[str, bool] = {}

        async def task_a():
            init_degradation_context()
            ctx = get_degradation_context()
            ctx.record(IntegrationResult.error(source="a", msg="fail"))
            await asyncio.sleep(0.01)
            results["a_has_degradation"] = get_degradation_context().has_degradation()
            clear_degradation_context()

        async def task_b():
            await asyncio.sleep(0.005)
            results["b_is_none"] = try_get_degradation_context() is None

        await asyncio.gather(task_a(), task_b())
        assert results["a_has_degradation"] is True
        assert results["b_is_none"] is True
