"""Tests for the event buffer."""

import asyncio
from unittest.mock import AsyncMock

from aal_sdk.buffer import EventBuffer
from aal_sdk.types import QueuedEvent


def _make_event(session_id: str = "sess-1", agent_id: str = "agent-1") -> QueuedEvent:
    """Create a test event."""
    return {
        "session_id": session_id,
        "agent_id": agent_id,
        "action_type": "TOOL_CALL",
        "status": "SUCCESS",
    }


async def test_push_adds_event_to_queue():
    """Test that push adds an event to the internal queue."""
    flush_fn = AsyncMock()
    buffer = EventBuffer(flush_fn, flush_interval=10.0, batch_size=100)
    # Don't start timer for this test

    buffer.push(_make_event())

    assert len(buffer._queue) == 1
    assert buffer._queue[0]["session_id"] == "sess-1"


async def test_flush_triggered_at_batch_size():
    """Test that flush is triggered when batch size is reached."""
    flush_fn = AsyncMock()
    buffer = EventBuffer(flush_fn, flush_interval=10.0, batch_size=3)
    buffer.start()  # Capture the running loop for run_coroutine_threadsafe

    buffer.push(_make_event("s1"))
    buffer.push(_make_event("s2"))
    assert len(buffer._queue) == 2
    assert flush_fn.call_count == 0

    # This should trigger a flush
    buffer.push(_make_event("s3"))

    # Queue should be empty after flush
    assert len(buffer._queue) == 0
    # Allow the async task to complete
    await asyncio.sleep(0.05)
    assert flush_fn.call_count == 1
    assert len(flush_fn.call_args[0][0]) == 3
    buffer.stop()


async def test_timer_based_flush():
    """Test that the timer triggers a flush."""
    flush_fn = AsyncMock()
    buffer = EventBuffer(flush_fn, flush_interval=0.05, batch_size=100)
    buffer.start()

    buffer.push(_make_event())
    assert len(buffer._queue) == 1

    # Wait for the timer to fire
    await asyncio.sleep(0.15)

    assert flush_fn.call_count >= 1
    buffer.stop()


async def test_drain_flushes_remaining_events():
    """Test that drain flushes all remaining events."""
    flush_fn = AsyncMock()
    buffer = EventBuffer(flush_fn, flush_interval=10.0, batch_size=100)
    buffer.start()

    buffer.push(_make_event("s1"))
    buffer.push(_make_event("s2"))

    await buffer.drain()

    assert flush_fn.call_count == 1
    assert len(flush_fn.call_args[0][0]) == 2
    assert len(buffer._queue) == 0


async def test_empty_drain_is_noop():
    """Test that draining an empty buffer doesn't call flush."""
    flush_fn = AsyncMock()
    buffer = EventBuffer(flush_fn, flush_interval=10.0, batch_size=100)
    buffer.start()

    await buffer.drain()

    assert flush_fn.call_count == 0


async def test_flush_error_doesnt_crash():
    """Test that a flush error is caught and doesn't crash."""
    flush_fn = AsyncMock(side_effect=Exception("Network error"))
    buffer = EventBuffer(flush_fn, flush_interval=10.0, batch_size=100)

    buffer.push(_make_event("s1"))
    buffer.push(_make_event("s2"))

    # drain calls flush_fn directly (no _safe_flush), so it will raise
    # Let's test _safe_flush instead
    await buffer._safe_flush([_make_event()])

    # Should not raise, just print to stderr
    assert flush_fn.call_count == 1
