"""Tests for GEOPipeline"""

import pytest

from agentkit.skills.geo_pipeline import (
    GEOPipeline,
    PipelineStep,
    PipelineStepResult,
    PipelineResult,
)


class MockAgent:
    """Mock Agent for pipeline testing"""

    def __init__(self, name: str, output_data: dict | None = None):
        self.name = name
        self.agent_type = "mock"
        self._output_data = output_data or {"result": f"output from {name}"}

    async def execute(self, task):
        from agentkit.core.protocol import TaskResult, TaskStatus
        from datetime import datetime, timezone
        now = datetime.now(timezone.utc)
        return TaskResult(
            task_id=task.task_id,
            agent_name=self.name,
            status=TaskStatus.COMPLETED,
            output_data=self._output_data,
            error_message=None,
            started_at=now,
            completed_at=now,
        )


class MockAgentPool:
    """Mock AgentPool"""

    def __init__(self, agents: dict[str, MockAgent] | None = None):
        self._agents = agents or {}

    def get_agent(self, name: str):
        return self._agents.get(name)

    def list_agents(self):
        return [{"name": a.name, "agent_type": a.agent_type} for a in self._agents.values()]


class TestGEOPipeline:
    """GEOPipeline unit tests"""

    @pytest.mark.asyncio
    async def test_sequential_pipeline(self):
        """Sequential steps should execute in order"""
        steps = [
            PipelineStep(name="step1", skill="skill_a"),
            PipelineStep(name="step2", skill="skill_b", depends_on=["step1"]),
        ]
        pool = MockAgentPool({
            "skill_a": MockAgent("skill_a", {"data": "result_a"}),
            "skill_b": MockAgent("skill_b", {"data": "result_b"}),
        })
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"query": "test"})
        assert result.success
        assert len(result.steps) == 2
        assert result.steps[0].status == "success"
        assert result.steps[1].status == "success"

    @pytest.mark.asyncio
    async def test_parallel_steps(self):
        """Steps without dependencies should execute in parallel"""
        steps = [
            PipelineStep(name="step1", skill="skill_a"),
            PipelineStep(name="step2", skill="skill_b"),
        ]
        pool = MockAgentPool({
            "skill_a": MockAgent("skill_a", {"data": "a"}),
            "skill_b": MockAgent("skill_b", {"data": "b"}),
        })
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"query": "test"})
        assert result.success
        assert len(result.steps) == 2

    @pytest.mark.asyncio
    async def test_dag_execution(self):
        """DAG with mixed parallel/sequential steps"""
        steps = [
            PipelineStep(name="detect", skill="skill_a"),
            PipelineStep(name="analyze_1", skill="skill_b", depends_on=["detect"]),
            PipelineStep(name="analyze_2", skill="skill_c", depends_on=["detect"]),
            PipelineStep(name="optimize", skill="skill_d", depends_on=["analyze_1", "analyze_2"]),
        ]
        pool = MockAgentPool({
            "skill_a": MockAgent("skill_a", {"citations": 5}),
            "skill_b": MockAgent("skill_b", {"competitor": "data"}),
            "skill_c": MockAgent("skill_c", {"trend": "up"}),
            "skill_d": MockAgent("skill_d", {"optimized": True}),
        })
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"brand": "TestBrand"})
        assert result.success
        assert len(result.steps) == 4

        # Check execution groups
        groups = pipeline._build_execution_groups()
        assert len(groups) == 3  # [detect], [analyze_1, analyze_2], [optimize]
        assert "detect" in groups[0]
        assert set(groups[1]) == {"analyze_1", "analyze_2"}
        assert groups[2] == ["optimize"]

    @pytest.mark.asyncio
    async def test_step_failure(self):
        """Failed step should be recorded"""
        class FailingAgent:
            name = "skill_a"
            agent_type = "mock"
            async def execute(self, task):
                raise RuntimeError("Agent failed")

        steps = [PipelineStep(name="step1", skill="skill_a")]
        pool = MockAgentPool({"skill_a": FailingAgent()})
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"query": "test"})
        assert not result.success
        assert result.steps[0].status == "failed"
        assert "Agent failed" in result.steps[0].error

    @pytest.mark.asyncio
    async def test_input_mapping(self):
        """Input mapping should resolve paths correctly"""
        steps = [
            PipelineStep(name="step1", skill="skill_a"),
            PipelineStep(
                name="step2",
                skill="skill_b",
                input_mapping={"brand": "$.input.brand"},
                depends_on=["step1"],
            ),
        ]
        pool = MockAgentPool({
            "skill_a": MockAgent("skill_a", {"data": "a"}),
            "skill_b": MockAgent("skill_b", {"data": "b"}),
        })
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"brand": "TestBrand"})
        assert result.success

    @pytest.mark.asyncio
    async def test_from_config(self):
        """Pipeline should be created from YAML config"""
        config = {
            "name": "geo_test",
            "steps": [
                {"name": "detect", "skill": "citation_detector"},
                {"name": "analyze", "skill": "competitor_analyzer", "depends_on": ["detect"]},
            ],
        }
        pipeline = GEOPipeline.from_config(config)
        assert pipeline.name == "geo_test"
        assert len(pipeline._steps) == 2
        assert pipeline._steps[1].depends_on == ["detect"]

    @pytest.mark.asyncio
    async def test_execution_groups_topological_sort(self):
        """Execution groups should follow topological order"""
        steps = [
            PipelineStep(name="a", skill="s1"),
            PipelineStep(name="b", skill="s2", depends_on=["a"]),
            PipelineStep(name="c", skill="s3", depends_on=["a"]),
            PipelineStep(name="d", skill="s4", depends_on=["b", "c"]),
        ]
        pipeline = GEOPipeline(name="test", steps=steps)

        groups = pipeline._build_execution_groups()
        assert len(groups) == 3
        assert groups[0] == ["a"]
        assert set(groups[1]) == {"b", "c"}
        assert groups[2] == ["d"]

    @pytest.mark.asyncio
    async def test_resolve_mapping_path(self):
        """Mapping path resolution"""
        input_data = {"brand": "TestBrand", "platforms": ["chatgpt"]}
        step_outputs = {
            "detect": {"citations": 5, "records": []},
        }

        # $.input.brand
        result = GEOPipeline._resolve_mapping_path("$.input.brand", input_data, step_outputs)
        assert result == "TestBrand"

        # $.steps.detect.output.citations
        result = GEOPipeline._resolve_mapping_path("$.steps.detect.output.citations", input_data, step_outputs)
        assert result == 5

        # $.steps.detect (whole output)
        result = GEOPipeline._resolve_mapping_path("$.steps.detect", input_data, step_outputs)
        assert result == {"citations": 5, "records": []}

    @pytest.mark.asyncio
    async def test_final_output_includes_all_steps(self):
        """Final output should include all step results"""
        steps = [
            PipelineStep(name="step1", skill="skill_a"),
            PipelineStep(name="step2", skill="skill_b", depends_on=["step1"]),
        ]
        pool = MockAgentPool({
            "skill_a": MockAgent("skill_a", {"result": "a"}),
            "skill_b": MockAgent("skill_b", {"result": "b"}),
        })
        pipeline = GEOPipeline(name="test", steps=steps, agent_pool=pool)

        result = await pipeline.execute({"query": "test"})
        assert "step1" in result.final_output
        assert "step2" in result.final_output
        assert "input" in result.final_output

    @pytest.mark.asyncio
    async def test_empty_pipeline(self):
        """Empty pipeline should succeed with no steps"""
        pipeline = GEOPipeline(name="empty", steps=[])
        result = await pipeline.execute({"query": "test"})
        assert result.success
        assert len(result.steps) == 0
