"""Pipeline 引擎单元测试"""
import pytest
import textwrap


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

SIMPLE_YAML = textwrap.dedent("""
name: test_pipeline
version: "1.0"
description: "单元测试用Pipeline"
variables:
  brand_name: "TestBrand"
stages:
  - name: step1
    agent: content_agent
    action: generate
    inputs:
      brand: "${brand_name}"
    outputs:
      - result
  - name: step2
    agent: review_agent
    action: review
    depends_on:
      - step1
    inputs:
      content: "${stages.step1.outputs.result}"
    outputs:
      - reviewed
""")

CYCLIC_YAML = textwrap.dedent("""
name: cyclic_pipeline
stages:
  - name: a
    agent: agent1
    action: act
    depends_on:
      - b
  - name: b
    agent: agent2
    action: act
    depends_on:
      - a
""")


# ---------------------------------------------------------------------------
# PipelineLoader 测试
# ---------------------------------------------------------------------------

class TestPipelineLoader:
    def test_load_valid_yaml(self):
        """加载正常 YAML 返回 Pipeline 对象"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.schema import Pipeline

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)

        assert isinstance(pipeline, Pipeline)
        assert pipeline.name == "test_pipeline"
        assert len(pipeline.stages) == 2
        assert pipeline.stages[0].name == "step1"
        assert pipeline.stages[1].name == "step2"

    def test_dag_validation_detects_cycle(self):
        """有环图抛出 PipelineCyclicError"""
        from app.agent_framework.pipeline.loader import PipelineLoader, PipelineCyclicError

        loader = PipelineLoader()
        with pytest.raises(PipelineCyclicError):
            loader.load_from_yaml(CYCLIC_YAML)

    def test_dag_validation_passes_acyclic(self):
        """无环图验证通过，不抛异常"""
        from app.agent_framework.pipeline.loader import PipelineLoader

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)
        assert pipeline is not None

    def test_topological_sort_order(self):
        """拓扑排序结果尊重依赖：step1 必须在 step2 之前"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.engine import PipelineEngine

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)

        engine = PipelineEngine(dispatcher=None)
        sorted_stages = engine._topological_sort(pipeline.stages)
        names = [s.name for s in sorted_stages]

        assert names.index("step1") < names.index("step2")

    def test_variable_resolution_simple(self):
        """${var} 简单变量替换"""
        from app.agent_framework.pipeline.loader import PipelineLoader

        result = PipelineLoader.resolve_variables("${brand_name}", {"brand_name": "MyBrand"})
        assert result == "MyBrand"

    def test_variable_resolution_nested(self):
        """${stages.step1.outputs.result} 嵌套路径解析"""
        from app.agent_framework.pipeline.loader import PipelineLoader

        context = {
            "stages": {
                "step1": {
                    "outputs": {
                        "result": "generated_content"
                    }
                }
            }
        }
        result = PipelineLoader.resolve_variables(
            "${stages.step1.outputs.result}", context
        )
        assert result == "generated_content"

    def test_variable_unresolved_kept(self):
        """未定义变量保持 ${var} 原样"""
        from app.agent_framework.pipeline.loader import PipelineLoader

        result = PipelineLoader.resolve_variables("${undefined_var}", {})
        assert result == "${undefined_var}"

    def test_variable_resolution_in_dict(self):
        """dict 中的变量引用被递归解析"""
        from app.agent_framework.pipeline.loader import PipelineLoader

        template = {"key": "${greeting}", "nested": {"val": "${name}"}}
        context = {"greeting": "Hello", "name": "World"}
        result = PipelineLoader.resolve_variables(template, context)

        assert result["key"] == "Hello"
        assert result["nested"]["val"] == "World"


# ---------------------------------------------------------------------------
# PipelineEngine 测试
# ---------------------------------------------------------------------------

class TestPipelineEngine:
    @pytest.mark.asyncio
    async def test_dry_run_mode(self):
        """dispatcher=None 时 dry-run 模式正常执行，返回 PipelineResult"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.engine import PipelineEngine
        from app.agent_framework.pipeline.schema import StageStatus

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)

        engine = PipelineEngine(dispatcher=None)
        result = await engine.execute(pipeline, context={"brand_name": "TestBrand"})

        assert result.pipeline_name == "test_pipeline"
        assert result.status == StageStatus.COMPLETED
        assert "step1" in result.stages_results
        assert "step2" in result.stages_results

    @pytest.mark.asyncio
    async def test_dry_run_stage_outputs(self):
        """dry-run 模式下每个 stage 的 outputs 有值"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.engine import PipelineEngine
        from app.agent_framework.pipeline.schema import StageStatus

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)

        engine = PipelineEngine(dispatcher=None)
        result = await engine.execute(pipeline)

        step1_result = result.stages_results["step1"]
        assert step1_result.status == StageStatus.COMPLETED
        assert "result" in step1_result.outputs

    def test_stage_timeout_config(self):
        """超时配置正确传递到 stage"""
        from app.agent_framework.pipeline.schema import PipelineStage

        stage = PipelineStage(
            name="timed_stage",
            agent="some_agent",
            action="do_action",
            timeout_seconds=60,
        )
        assert stage.timeout_seconds == 60

    def test_stage_retry_count(self):
        """重试次数配置正确"""
        from app.agent_framework.pipeline.schema import PipelineStage

        stage = PipelineStage(
            name="retry_stage",
            agent="some_agent",
            action="do_action",
            retry_count=3,
        )
        assert stage.retry_count == 3

    @pytest.mark.asyncio
    async def test_pipeline_variables_override(self):
        """外部 context 变量覆盖 pipeline 默认变量"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.engine import PipelineEngine

        loader = PipelineLoader()
        pipeline = loader.load_from_yaml(SIMPLE_YAML)

        # 使用外部 context 覆盖 brand_name
        engine = PipelineEngine(dispatcher=None)
        result = await engine.execute(pipeline, context={"brand_name": "OverrideBrand"})

        # 执行不应失败
        from app.agent_framework.pipeline.schema import StageStatus
        assert result.status == StageStatus.COMPLETED

    def test_load_error_on_invalid_yaml(self):
        """加载无效 YAML 抛出 PipelineLoadError"""
        from app.agent_framework.pipeline.loader import PipelineLoader, PipelineLoadError

        loader = PipelineLoader()
        with pytest.raises(PipelineLoadError):
            loader.load_from_yaml("not: valid: yaml: [[[")

    def test_validate_dag_acyclic(self):
        """validate_dag 对无环图返回 True"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.schema import PipelineStage

        stages = [
            PipelineStage(name="a", agent="ag", action="act", depends_on=[]),
            PipelineStage(name="b", agent="ag", action="act", depends_on=["a"]),
            PipelineStage(name="c", agent="ag", action="act", depends_on=["b"]),
        ]
        assert PipelineLoader.validate_dag(stages) is True

    def test_validate_dag_cyclic(self):
        """validate_dag 对有环图返回 False"""
        from app.agent_framework.pipeline.loader import PipelineLoader
        from app.agent_framework.pipeline.schema import PipelineStage

        stages = [
            PipelineStage(name="a", agent="ag", action="act", depends_on=["b"]),
            PipelineStage(name="b", agent="ag", action="act", depends_on=["a"]),
        ]
        assert PipelineLoader.validate_dag(stages) is False
