"""Integration tests for DashboardEventCollector with orchestrator.

Tests verify complete agent lifecycle event capture in realistic scenarios.
"""

import asyncio

import pytest
from pydantic import BaseModel

from flock.components import EngineComponent
from flock.dashboard.collector import DashboardEventCollector
from flock.dashboard.events import (
    AgentCompletedEvent,
    MessagePublishedEvent,
)
from flock.orchestrator import Flock
from flock.runtime import EvalInputs, EvalResult


class Idea(BaseModel):
    """Test input artifact."""

    topic: str
    genre: str


class Movie(BaseModel):
    """Test intermediate artifact."""

    title: str
    runtime: int
    synopsis: str


class Tagline(BaseModel):
    """Test output artifact."""

    tagline: str


class MovieEngine(EngineComponent):
    """Test engine that generates movie from idea."""

    async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
        idea = inputs.artifacts[0].payload
        movie = Movie(
            title=f"{idea['topic'].upper()} - THE MOVIE",
            runtime=120,
            synopsis=f"A {idea['genre']} about {idea['topic']}.",
        )
        return EvalResult.from_object(movie, agent=agent)


class TaglineEngine(EngineComponent):
    """Test engine that generates tagline from movie."""

    async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
        movie = inputs.artifacts[0].payload
        tagline = Tagline(tagline=f"{movie['title']}: Coming Soon!")
        return EvalResult.from_object(tagline, agent=agent)


@pytest.mark.asyncio
async def test_full_lifecycle_event_capture():
    """Test E2E lifecycle: agent activated → messages published → agent completed."""
    # Create orchestrator with collector
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create movie pipeline with collector attached
    movie_agent = (
        orchestrator.agent("movie")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(MovieEngine())
    )

    (
        orchestrator.agent("tagline")
        .consumes(Movie)
        .with_utilities(collector)
        .with_engines(TaglineEngine())
    )

    # Run pipeline
    idea = Idea(topic="AI cats", genre="comedy")
    await orchestrator.invoke(movie_agent, idea)
    await orchestrator.run_until_idle()

    # Verify events were captured in order
    assert len(collector.events) > 0

    # Find event types
    event_types = [type(event).__name__ for event in collector.events]

    # Should have agent_activated for both agents
    activated_count = event_types.count("AgentActivatedEvent")
    assert activated_count >= 1  # At least movie agent activated

    # Should have message_published events
    published_count = event_types.count("MessagePublishedEvent")
    assert published_count >= 1  # At least Movie artifact published

    # Should have agent_completed events
    completed_count = event_types.count("AgentCompletedEvent")
    assert completed_count >= 1  # At least movie agent completed

    # Verify event ordering (activated before completed for same agent)
    movie_events = [
        e for e in collector.events if hasattr(e, "agent_name") and e.agent_name == "movie"
    ]
    if len(movie_events) >= 2:
        # First should be activated or message, last should be completed
        assert isinstance(movie_events[-1], AgentCompletedEvent)


@pytest.mark.asyncio
async def test_correlation_id_flow_through_pipeline():
    """Test that correlation_id flows through entire agent pipeline."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create pipeline
    movie_agent = (
        orchestrator.agent("movie")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(MovieEngine())
    )

    (
        orchestrator.agent("tagline")
        .consumes(Movie)
        .with_utilities(collector)
        .with_engines(TaglineEngine())
    )

    # Run pipeline
    idea = Idea(topic="robots", genre="action")

    await orchestrator.invoke(movie_agent, idea)
    await orchestrator.run_until_idle()

    # Verify all events have correlation_id (they should all match the one from the input artifact)
    # Get correlation_id from first event
    if len(collector.events) > 0:
        first_correlation_id = collector.events[0].correlation_id
        for event in collector.events:
            assert event.correlation_id == first_correlation_id


@pytest.mark.asyncio
async def test_error_event_capture():
    """Test that agent errors are captured correctly."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create engine that fails
    class FailingEngine(EngineComponent):
        async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
            raise ValueError("Intentional test error")

    # Create agent with failing engine
    failing_agent = (
        orchestrator.agent("failing_agent")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(FailingEngine())
    )

    # Run and expect error
    idea = Idea(topic="test", genre="test")

    with pytest.raises(ValueError, match="Intentional test error"):
        await orchestrator.invoke(failing_agent, idea)

    # Verify error event was captured
    from flock.dashboard.events import AgentErrorEvent

    error_events = [e for e in collector.events if isinstance(e, AgentErrorEvent)]
    assert len(error_events) == 1

    error_event = error_events[0]
    assert error_event.agent_name == "failing_agent"
    assert error_event.error_type == "ValueError"
    assert "Intentional test error" in error_event.error_message
    assert error_event.traceback is not None


@pytest.mark.asyncio
async def test_multiple_runs_accumulate_events():
    """Test that multiple agent runs accumulate events in buffer."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create simple agent
    class EchoEngine(EngineComponent):
        async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
            return EvalResult(artifacts=inputs.artifacts)

    agent = (
        orchestrator.agent("echo")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(EchoEngine())
    )

    # Run multiple times
    for i in range(5):
        idea = Idea(topic=f"test-{i}", genre="test")
        await orchestrator.invoke(agent, idea)

    # Verify events accumulated (at least 5 completed events)
    from flock.dashboard.events import AgentCompletedEvent

    completed_events = [e for e in collector.events if isinstance(e, AgentCompletedEvent)]
    assert len(completed_events) >= 5


@pytest.mark.asyncio
async def test_concurrent_agents_event_capture():
    """Test that concurrent agent executions are tracked correctly."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create two independent agents with higher concurrency
    class FastEngine(EngineComponent):
        async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
            await asyncio.sleep(0.01)  # Small delay
            return EvalResult(artifacts=inputs.artifacts)

    agent1 = (
        orchestrator.agent("agent1")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(FastEngine())
    )

    agent2 = (
        orchestrator.agent("agent2")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(FastEngine())
    )

    # Run concurrently
    idea1 = Idea(topic="topic1", genre="genre1")
    idea2 = Idea(topic="topic2", genre="genre2")

    await asyncio.gather(
        orchestrator.invoke(agent1, idea1),
        orchestrator.invoke(agent2, idea2),
    )

    # Verify events from both agents
    agent1_events = [
        e for e in collector.events if hasattr(e, "agent_name") and e.agent_name == "agent1"
    ]
    agent2_events = [
        e for e in collector.events if hasattr(e, "agent_name") and e.agent_name == "agent2"
    ]

    assert len(agent1_events) > 0
    assert len(agent2_events) > 0


@pytest.mark.asyncio
async def test_message_published_contains_artifact_payload():
    """Test that message_published events contain complete artifact payload."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    movie_agent = (
        orchestrator.agent("movie")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(MovieEngine())
    )

    idea = Idea(topic="dragons", genre="fantasy")
    await orchestrator.invoke(movie_agent, idea)

    # Find message_published events
    message_events = [e for e in collector.events if isinstance(e, MessagePublishedEvent)]

    # Should have at least one artifact published
    assert len(message_events) >= 1

    # Find Movie artifact if it exists, or verify any artifact has payload
    movie_messages = [e for e in message_events if e.artifact_type == "Movie"]
    if len(movie_messages) > 0:
        # Verify Movie payload is complete
        movie_event = movie_messages[0]
        assert "title" in movie_event.payload
        assert "runtime" in movie_event.payload
        assert "synopsis" in movie_event.payload
        assert "DRAGONS" in movie_event.payload["title"].upper()
    else:
        # At minimum, verify some artifact was published with a payload
        assert isinstance(message_events[0].payload, dict)


@pytest.mark.asyncio
async def test_agent_completed_includes_metrics():
    """Test that agent_completed events include execution metrics."""
    orchestrator = Flock()
    collector = DashboardEventCollector()

    # Create engine that sets metrics
    class MetricsEngine(EngineComponent):
        async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult:
            result = EvalResult(artifacts=inputs.artifacts)
            result.metrics = {
                "test_metric": 42,
                "execution_count": 1,
            }
            return result

    agent = (
        orchestrator.agent("metrics_agent")
        .consumes(Idea)
        .with_utilities(collector)
        .with_engines(MetricsEngine())
    )

    idea = Idea(topic="test", genre="test")
    await orchestrator.invoke(agent, idea)

    # Find completed event
    completed_events = [e for e in collector.events if isinstance(e, AgentCompletedEvent)]
    assert len(completed_events) >= 1

    # Verify metrics are captured
    completed_event = completed_events[0]
    # Note: Metrics may or may not be populated depending on orchestrator implementation
    # This test verifies the field exists and is a dict
    assert isinstance(completed_event.metrics, dict)
