"""Tests for quick-win fixes: schema caching, atomic claim, swarm error isolation."""
from __future__ import annotations


import pytest

from logos_adk.event_bus import InMemoryEventBus, SQLiteEventBus


# ── Fix #1: Schema caching ──────────────────────────────────


def test_sqlite_schema_cached_after_first_call(tmp_path):
    """_ensure_schema should only run DDL once, then skip."""
    bus = SQLiteEventBus(str(tmp_path / "test.db"))
    assert bus._schema_ready is False

    # First operation triggers schema setup
    bus.publish("test", {"data": 1})
    assert bus._schema_ready is True

    # Second operation should skip schema setup
    # (verified by the flag — DDL is not re-executed)
    bus.publish("test", {"data": 2})
    assert bus._schema_ready is True


def test_sqlite_schema_flag_survives_multiple_operations(tmp_path):
    """All operations after first should skip schema check."""
    bus = SQLiteEventBus(str(tmp_path / "test.db"))

    bus.publish("t", {"x": 1})
    assert bus._schema_ready is True

    claimed = bus.claim("worker", ["t"])
    assert claimed is not None
    assert bus._schema_ready is True

    bus.ack(claimed.id, "worker")
    assert bus._schema_ready is True

    stored = bus.get_event(claimed.id)
    assert stored is not None
    assert stored.status == "completed"


def test_postgresql_schema_cached(monkeypatch):
    from logos_adk.event_bus import PostgreSQLEventBus
    bus = PostgreSQLEventBus("postgresql://user:pass@localhost/db")
    assert bus._schema_ready is False


# ── Fix #2: Atomic claim (tested via fake backend in test_event_bus.py) ──


def test_sqlite_claim_returns_none_when_no_events(tmp_path):
    """Claim on empty bus returns None."""
    bus = SQLiteEventBus(str(tmp_path / "test.db"))
    bus.publish("other_topic", {"data": 1})

    result = bus.claim("worker", ["nonexistent"])
    assert result is None


def test_sqlite_claim_works_correctly(tmp_path):
    """Basic claim flow still works after schema caching."""
    bus = SQLiteEventBus(str(tmp_path / "test.db"))
    pub = bus.publish("test.claim", {"val": 42})

    claimed = bus.claim("worker", ["test.claim"])
    assert claimed is not None
    assert claimed.id == pub.id
    assert claimed.status == "claimed"
    assert claimed.attempt == 1

    # Cannot claim again
    second = bus.claim("worker2", ["test.claim"])
    assert second is None


# ── Fix #3: Swarm error isolation ────────────────────────────


@pytest.mark.asyncio
async def test_swarm_drain_continues_after_handler_error():
    """A failing handler should NOT prevent other handlers from running."""
    from logos_adk.swarm import Swarm

    bus = InMemoryEventBus()
    swarm = Swarm("test-swarm", event_bus=bus, retry_delay_seconds=0.0)

    results = []

    async def failing_handler(event):
        raise RuntimeError("handler broke")

    async def working_handler(event):
        results.append(event.payload)
        return {"ok": True}

    swarm.subscribe("fail.topic", failing_handler)
    swarm.subscribe("work.topic", working_handler)

    bus.publish("fail.topic", {"should": "fail"})
    bus.publish("work.topic", {"should": "succeed"})

    processed = await swarm.drain(max_events=10)

    # Both events were processed (claimed + attempted)
    assert processed == 2
    # Working handler ran successfully despite failing handler
    assert len(results) == 1
    assert results[0]["should"] == "succeed"

    # Failed event was requeued
    fail_events = bus.list_events(topic="fail.topic")
    assert fail_events[0].status == "queued"
    assert fail_events[0].last_error is not None
    assert "handler broke" in fail_events[0].last_error


@pytest.mark.asyncio
async def test_swarm_drain_counts_failed_events_as_processed():
    """Failed events should still count toward max_events limit."""
    from logos_adk.swarm import Swarm

    bus = InMemoryEventBus()
    swarm = Swarm("test-swarm", event_bus=bus, retry_delay_seconds=0.0)

    async def always_fail(event):
        raise ValueError("nope")

    swarm.subscribe("bad.topic", always_fail)
    bus.publish("bad.topic", {"data": 1})

    processed = await swarm.drain(max_events=5)
    assert processed == 1  # Event was processed (and failed)


@pytest.mark.asyncio
async def test_swarm_dispatch_events_still_raises():
    """dispatch_events uses its own inline processing loop — errors still propagate there."""
    from logos_adk.swarm import Swarm

    bus = InMemoryEventBus()
    swarm = Swarm("test-swarm", event_bus=bus, retry_delay_seconds=0.0)

    async def fail(event):
        raise RuntimeError("boom")

    swarm.subscribe("topic", fail, publish_to="response")

    with pytest.raises(RuntimeError, match="boom"):
        async for _ in swarm.dispatch_events(
            "test",
            request_topic="topic",
            response_topic="response",
        ):
            pass


@pytest.mark.asyncio
async def test_swarm_drain_multiple_failures_all_requeued():
    """Multiple failing handlers all get requeued, processing continues."""
    from logos_adk.swarm import Swarm

    bus = InMemoryEventBus()
    swarm = Swarm("test-swarm", event_bus=bus, retry_delay_seconds=0.0)

    successes = []

    async def fail_a(event):
        raise TypeError("A broke")

    async def fail_b(event):
        raise ValueError("B broke")

    async def succeed_c(event):
        successes.append("C")
        return {"ok": True}

    swarm.subscribe("a", fail_a)
    swarm.subscribe("b", fail_b)
    swarm.subscribe("c", succeed_c)

    bus.publish("a", {"x": 1})
    bus.publish("b", {"x": 2})
    bus.publish("c", {"x": 3})

    processed = await swarm.drain(max_events=10)

    assert processed == 3
    assert successes == ["C"]

    # Both failed events requeued
    a_events = bus.list_events(topic="a")
    assert a_events[0].status == "queued"
    assert "A broke" in a_events[0].last_error

    b_events = bus.list_events(topic="b")
    assert b_events[0].status == "queued"
    assert "B broke" in b_events[0].last_error

    # Successful event completed
    c_events = bus.list_events(topic="c")
    assert c_events[0].status == "completed"
