"""
Unit tests for the workflow system.
"""

import asyncio
import pytest
from datetime import datetime

from agent_orchestrator.workflow.models import (
    Workflow,
    WorkflowStep,
    StepType,
    StepStatus,
    WorkflowStatus,
    Condition,
    ConditionOperator,
)
from agent_orchestrator.workflow.engine import (
    WorkflowEngine,
    WorkflowContext,
)


class TestCondition:
    """Tests for Condition evaluation."""

    def test_equals_condition(self):
        """Test equals operator."""
        cond = Condition(variable="status", operator="eq", value="success")
        assert cond.evaluate({"status": "success"}) is True
        assert cond.evaluate({"status": "failed"}) is False

    def test_not_equals_condition(self):
        """Test not equals operator."""
        cond = Condition(variable="status", operator="ne", value="failed")
        assert cond.evaluate({"status": "success"}) is True
        assert cond.evaluate({"status": "failed"}) is False

    def test_greater_than_condition(self):
        """Test greater than operator."""
        cond = Condition(variable="count", operator="gt", value=5)
        assert cond.evaluate({"count": 10}) is True
        assert cond.evaluate({"count": 3}) is False

    def test_less_than_condition(self):
        """Test less than operator."""
        cond = Condition(variable="count", operator="lt", value=5)
        assert cond.evaluate({"count": 3}) is True
        assert cond.evaluate({"count": 10}) is False

    def test_contains_condition(self):
        """Test contains operator."""
        cond = Condition(variable="message", operator="contains", value="error")
        assert cond.evaluate({"message": "An error occurred"}) is True
        assert cond.evaluate({"message": "All good"}) is False

    def test_is_true_condition(self):
        """Test is_true operator."""
        cond = Condition(variable="enabled", operator="is_true")
        assert cond.evaluate({"enabled": True}) is True
        assert cond.evaluate({"enabled": False}) is False
        assert cond.evaluate({"enabled": 1}) is True

    def test_exists_condition(self):
        """Test exists operator."""
        cond = Condition(variable="data", operator="exists")
        assert cond.evaluate({"data": "value"}) is True
        assert cond.evaluate({"other": "value"}) is False

    def test_nested_variable(self):
        """Test nested variable access."""
        cond = Condition(variable="result.status", operator="eq", value="ok")
        assert cond.evaluate({"result": {"status": "ok"}}) is True
        assert cond.evaluate({"result": {"status": "error"}}) is False


class TestWorkflowStep:
    """Tests for WorkflowStep model."""

    def test_task_step_creation(self):
        """Test creating a task step."""
        step = WorkflowStep(
            id="step-1",
            name="Build project",
            step_type="task",
            task_type="build",
            task_description="Build the application",
            timeout_minutes=30,
        )
        assert step.id == "step-1"
        assert step.step_type == "task"
        assert step.task_type == "build"

    def test_step_to_dict(self):
        """Test converting step to dictionary."""
        step = WorkflowStep(
            id="step-1",
            name="Test step",
            step_type="task",
            task_type="test",
            task_description="Run tests",
            depends_on=["build"],
        )
        data = step.to_dict()
        assert data["id"] == "step-1"
        assert data["name"] == "Test step"
        assert data["type"] == "task"
        assert data["depends_on"] == ["build"]

    def test_step_from_dict(self):
        """Test creating step from dictionary."""
        data = {
            "id": "step-2",
            "name": "Deploy",
            "type": "task",
            "task": {
                "type": "deploy",
                "description": "Deploy to production",
                "timeout_minutes": 45,
            },
            "depends_on": ["test"],
            "retry": {"max": 3, "delay_seconds": 60},
        }
        step = WorkflowStep.from_dict(data)
        assert step.id == "step-2"
        assert step.task_type == "deploy"
        assert step.timeout_minutes == 45
        assert step.depends_on == ["test"]
        assert step.max_retries == 3

    def test_parallel_step(self):
        """Test parallel step with sub-steps."""
        step = WorkflowStep(
            id="parallel-1",
            name="Parallel tests",
            step_type="parallel",
            parallel_steps=[
                WorkflowStep(id="unit", name="Unit tests", step_type="task"),
                WorkflowStep(id="integration", name="Integration tests", step_type="task"),
            ],
        )
        assert step.step_type == "parallel"
        assert len(step.parallel_steps) == 2

    def test_conditional_step(self):
        """Test conditional step."""
        step = WorkflowStep(
            id="cond-1",
            name="Check result",
            step_type="conditional",
            condition=Condition("steps.test.status", "eq", "completed"),
            on_true=WorkflowStep(id="deploy", name="Deploy", step_type="task"),
            on_false=WorkflowStep(id="notify", name="Notify failure", step_type="task"),
        )
        assert step.step_type == "conditional"
        assert step.condition is not None
        assert step.on_true.id == "deploy"
        assert step.on_false.id == "notify"


class TestWorkflow:
    """Tests for Workflow model."""

    def test_workflow_creation(self):
        """Test creating a workflow."""
        workflow = Workflow(
            id="wf-1",
            name="CI/CD Pipeline",
            description="Build, test, and deploy",
            steps=[
                WorkflowStep(id="build", name="Build", step_type="task"),
                WorkflowStep(id="test", name="Test", step_type="task", depends_on=["build"]),
            ],
        )
        assert workflow.id == "wf-1"
        assert len(workflow.steps) == 2
        assert workflow.status == "draft"

    def test_workflow_to_json(self):
        """Test converting workflow to JSON."""
        workflow = Workflow(
            id="wf-1",
            name="Test Workflow",
            steps=[
                WorkflowStep(id="step-1", name="Step 1", step_type="task"),
            ],
            variables={"env": "production"},
        )
        json_str = workflow.to_json()
        assert '"id": "wf-1"' in json_str
        assert '"env": "production"' in json_str

    def test_workflow_from_json(self):
        """Test creating workflow from JSON."""
        json_str = '''
        {
            "id": "wf-2",
            "name": "Loaded Workflow",
            "version": "2.0",
            "steps": [
                {"id": "s1", "name": "First", "type": "task"},
                {"id": "s2", "name": "Second", "type": "task", "depends_on": ["s1"]}
            ],
            "variables": {"key": "value"},
            "settings": {
                "timeout_minutes": 60,
                "on_failure": "continue"
            }
        }
        '''
        workflow = Workflow.from_json(json_str)
        assert workflow.id == "wf-2"
        assert workflow.version == "2.0"
        assert len(workflow.steps) == 2
        assert workflow.steps[1].depends_on == ["s1"]
        assert workflow.on_failure == "continue"

    def test_get_step(self):
        """Test getting a step by ID."""
        workflow = Workflow(
            id="wf-1",
            name="Test",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task"),
                WorkflowStep(id="b", name="B", step_type="task"),
            ],
        )
        assert workflow.get_step("a").name == "A"
        assert workflow.get_step("b").name == "B"
        assert workflow.get_step("c") is None

    def test_get_ready_steps(self):
        """Test getting steps ready for execution."""
        workflow = Workflow(
            id="wf-1",
            name="Test",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task", status="pending"),
                WorkflowStep(id="b", name="B", step_type="task", status="pending", depends_on=["a"]),
                WorkflowStep(id="c", name="C", step_type="task", status="pending"),
            ],
        )
        ready = workflow.get_ready_steps()
        assert len(ready) == 2  # a and c are ready
        assert {s.id for s in ready} == {"a", "c"}

        # Mark a as completed
        workflow.steps[0].status = "completed"
        ready = workflow.get_ready_steps()
        assert len(ready) == 2  # b and c are ready
        assert {s.id for s in ready} == {"b", "c"}

    def test_is_complete(self):
        """Test checking if workflow is complete."""
        workflow = Workflow(
            id="wf-1",
            name="Test",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task", status="completed"),
                WorkflowStep(id="b", name="B", step_type="task", status="pending"),
            ],
        )
        assert workflow.is_complete() is False

        workflow.steps[1].status = "completed"
        assert workflow.is_complete() is True

    def test_has_failures(self):
        """Test checking for failures."""
        workflow = Workflow(
            id="wf-1",
            name="Test",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task", status="completed"),
                WorkflowStep(id="b", name="B", step_type="task", status="failed"),
            ],
        )
        assert workflow.has_failures() is True


class TestWorkflowContext:
    """Tests for WorkflowContext."""

    def test_variable_access(self):
        """Test getting and setting variables."""
        ctx = WorkflowContext(
            workflow_id="wf-1",
            variables={"env": "prod", "count": 5},
        )
        assert ctx.get("env") == "prod"
        assert ctx.get("count") == 5
        assert ctx.get("missing", "default") == "default"

        ctx.set("new_var", "new_value")
        assert ctx.get("new_var") == "new_value"

    def test_step_result_access(self):
        """Test accessing step results."""
        ctx = WorkflowContext(workflow_id="wf-1")
        ctx.set_step_result("step-1", "completed", {"output": "data"})

        assert ctx.get("steps.step-1.status") == "completed"
        assert ctx.get("steps.step-1.result.output") == "data"

    def test_interpolation(self):
        """Test variable interpolation."""
        ctx = WorkflowContext(
            workflow_id="wf-1",
            variables={"name": "test", "version": "1.0"},
        )
        result = ctx.interpolate("Deploying ${name} version ${version}")
        assert result == "Deploying test version 1.0"

    def test_interpolation_with_step_results(self):
        """Test interpolation with step results."""
        ctx = WorkflowContext(workflow_id="wf-1")
        ctx.set_step_result("build", "completed", {"artifact": "app.jar"})

        result = ctx.interpolate("Deploying ${steps.build.result.artifact}")
        assert result == "Deploying app.jar"


class TestWorkflowEngine:
    """Tests for WorkflowEngine."""

    @pytest.fixture
    def engine(self) -> WorkflowEngine:
        """Create a test engine."""
        return WorkflowEngine()

    @pytest.mark.asyncio
    async def test_execute_simple_workflow(self, engine: WorkflowEngine):
        """Test executing a simple workflow."""
        workflow = Workflow(
            id="wf-1",
            name="Simple",
            steps=[
                WorkflowStep(id="a", name="Step A", step_type="task"),
                WorkflowStep(id="b", name="Step B", step_type="task", depends_on=["a"]),
            ],
        )

        ctx = await engine.execute(workflow)

        assert workflow.status == "completed"
        assert workflow.steps[0].status == "completed"
        assert workflow.steps[1].status == "completed"
        assert "a" in ctx.step_results
        assert "b" in ctx.step_results

    @pytest.mark.asyncio
    async def test_execute_parallel_steps(self, engine: WorkflowEngine):
        """Test executing steps in parallel."""
        workflow = Workflow(
            id="wf-1",
            name="Parallel",
            steps=[
                WorkflowStep(id="setup", name="Setup", step_type="task"),
                WorkflowStep(id="p1", name="Parallel 1", step_type="task", depends_on=["setup"]),
                WorkflowStep(id="p2", name="Parallel 2", step_type="task", depends_on=["setup"]),
                WorkflowStep(id="p3", name="Parallel 3", step_type="task", depends_on=["setup"]),
                WorkflowStep(id="finish", name="Finish", step_type="task", depends_on=["p1", "p2", "p3"]),
            ],
            max_concurrent_steps=3,
        )

        await engine.execute(workflow)

        assert workflow.status == "completed"
        assert all(s.status == "completed" for s in workflow.steps)

    @pytest.mark.asyncio
    async def test_execute_conditional_step(self, engine: WorkflowEngine):
        """Test executing conditional step."""
        workflow = Workflow(
            id="wf-1",
            name="Conditional",
            variables={"should_deploy": True},
            steps=[
                WorkflowStep(id="build", name="Build", step_type="task"),
                WorkflowStep(
                    id="check",
                    name="Check deploy",
                    step_type="conditional",
                    condition=Condition("vars.should_deploy", "is_true"),
                    on_true=WorkflowStep(id="deploy", name="Deploy", step_type="task"),
                    on_false=WorkflowStep(id="skip", name="Skip", step_type="task"),
                    depends_on=["build"],
                ),
            ],
        )

        ctx = await engine.execute(workflow)

        assert workflow.status == "completed"
        # The conditional should have executed on_true
        assert ctx.step_results["check"]["result"]["condition_result"] is True

    @pytest.mark.asyncio
    async def test_execute_with_custom_task_executor(self):
        """Test with custom task executor."""
        executed_tasks = []

        async def task_executor(**kwargs):
            executed_tasks.append(kwargs)
            return {"status": "done", "task": kwargs.get("description")}

        engine = WorkflowEngine(task_executor=task_executor)

        workflow = Workflow(
            id="wf-1",
            name="Custom",
            steps=[
                WorkflowStep(
                    id="task-1",
                    name="Custom Task",
                    step_type="task",
                    task_type="build",
                    task_description="Build the app",
                ),
            ],
        )

        await engine.execute(workflow)

        assert len(executed_tasks) == 1
        assert executed_tasks[0]["description"] == "Build the app"

    @pytest.mark.asyncio
    async def test_execute_with_variables(self, engine: WorkflowEngine):
        """Test workflow with variable interpolation."""
        workflow = Workflow(
            id="wf-1",
            name="Variables",
            variables={"app_name": "myapp"},
            steps=[
                WorkflowStep(
                    id="deploy",
                    name="Deploy",
                    step_type="task",
                    task_description="Deploy ${app_name} to production",
                ),
            ],
        )

        ctx = await engine.execute(workflow, {"environment": "prod"})

        assert ctx.get("app_name") == "myapp"
        assert ctx.get("environment") == "prod"

    def test_validate_workflow(self, engine: WorkflowEngine):
        """Test workflow validation."""
        # Valid workflow
        valid = Workflow(
            id="wf-1",
            name="Valid",
            steps=[WorkflowStep(id="a", name="A", step_type="task")],
        )
        assert engine.validate_workflow(valid) == []

        # Missing ID
        invalid1 = Workflow(id="", name="No ID", steps=[])
        errors = engine.validate_workflow(invalid1)
        assert any("ID" in e for e in errors)

        # Duplicate step IDs
        invalid2 = Workflow(
            id="wf-2",
            name="Duplicate",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task"),
                WorkflowStep(id="a", name="A2", step_type="task"),
            ],
        )
        errors = engine.validate_workflow(invalid2)
        assert any("Duplicate" in e for e in errors)

    def test_validate_circular_dependencies(self, engine: WorkflowEngine):
        """Test detection of circular dependencies."""
        workflow = Workflow(
            id="wf-1",
            name="Circular",
            steps=[
                WorkflowStep(id="a", name="A", step_type="task", depends_on=["c"]),
                WorkflowStep(id="b", name="B", step_type="task", depends_on=["a"]),
                WorkflowStep(id="c", name="C", step_type="task", depends_on=["b"]),
            ],
        )
        errors = engine.validate_workflow(workflow)
        assert any("Circular" in e for e in errors)

    @pytest.mark.asyncio
    async def test_failure_handling_stop(self, engine: WorkflowEngine):
        """Test workflow stops on failure with on_failure=stop."""
        async def failing_executor(**kwargs):
            if "fail" in kwargs.get("description", ""):
                raise Exception("Intentional failure")
            return {"status": "ok"}

        engine = WorkflowEngine(task_executor=failing_executor)

        workflow = Workflow(
            id="wf-1",
            name="Failing",
            on_failure="stop",
            steps=[
                WorkflowStep(
                    id="fail",
                    name="Fail",
                    step_type="task",
                    task_description="fail here",
                ),
                WorkflowStep(
                    id="after",
                    name="After",
                    step_type="task",
                    task_description="should not run",
                    depends_on=["fail"],
                ),
            ],
        )

        await engine.execute(workflow)

        assert workflow.status == "failed"
        assert workflow.steps[0].status == "failed"
        assert workflow.steps[1].status == "pending"  # Never ran

    @pytest.mark.asyncio
    async def test_step_outputs(self):
        """Test step output extraction."""
        async def executor(**kwargs):
            return {"build": {"artifact": "app-1.0.jar", "size": 1024}}

        engine = WorkflowEngine(task_executor=executor)

        workflow = Workflow(
            id="wf-1",
            name="Outputs",
            steps=[
                WorkflowStep(
                    id="build",
                    name="Build",
                    step_type="task",
                    task_description="Build",
                    outputs={"artifact_path": "build.artifact"},
                ),
            ],
        )

        ctx = await engine.execute(workflow)

        assert ctx.get("artifact_path") == "app-1.0.jar"


class TestWorkflowIntegration:
    """Integration tests for complete workflows."""

    @pytest.mark.asyncio
    async def test_ci_cd_workflow(self):
        """Test a realistic CI/CD workflow."""
        workflow_json = '''
        {
            "id": "ci-cd",
            "name": "CI/CD Pipeline",
            "version": "1.0",
            "variables": {
                "project": "myapp",
                "environment": "staging"
            },
            "steps": [
                {
                    "id": "checkout",
                    "name": "Checkout code",
                    "type": "task",
                    "task": {"type": "git", "description": "Checkout ${project}"}
                },
                {
                    "id": "build",
                    "name": "Build",
                    "type": "task",
                    "task": {"type": "build", "description": "Build project"},
                    "depends_on": ["checkout"]
                },
                {
                    "id": "test",
                    "name": "Run tests",
                    "type": "task",
                    "task": {"type": "test", "description": "Run test suite"},
                    "depends_on": ["build"],
                    "retry": {"max": 2, "delay_seconds": 5}
                },
                {
                    "id": "deploy",
                    "name": "Deploy",
                    "type": "task",
                    "task": {"type": "deploy", "description": "Deploy to ${environment}"},
                    "depends_on": ["test"]
                }
            ],
            "settings": {
                "timeout_minutes": 30,
                "on_failure": "stop"
            }
        }
        '''

        workflow = Workflow.from_json(workflow_json)
        engine = WorkflowEngine()

        ctx = await engine.execute(workflow)

        assert workflow.status == "completed"
        assert len(ctx.step_results) == 4
        assert all(r["status"] == "completed" for r in ctx.step_results.values())
