"""Tests for DAG (Directed Acyclic Graph) execution module."""

from __future__ import annotations

import asyncio
import time
from datetime import datetime

import pytest

from oclawma.dag import (
    DAG,
    DAGCycleError,
    DAGError,
    DAGExecutor,
    DAGJob,
    DAGJobResult,
    DAGNotFoundError,
    DAGProgress,
    DAGResult,
    DAGStatus,
    execute_dag,
    execute_dag_async,
)
from oclawma.queue.models import JobPriority


class TestDAGJob:
    """Test DAGJob dataclass."""

    def test_default_creation(self):
        """Test creating a DAGJob with defaults."""
        job = DAGJob(id="test-job")

        assert job.id == "test-job"
        assert job.payload == {}
        assert job.priority == JobPriority.NORMAL
        assert job.depends_on == []
        assert job.max_retries == 3
        assert job.job_type == "default"
        assert job.metadata == {}

    def test_custom_creation(self):
        """Test creating a DAGJob with custom values."""
        job = DAGJob(
            id="custom-job",
            payload={"key": "value"},
            priority=JobPriority.HIGH,
            depends_on=["dep1", "dep2"],
            max_retries=5,
            job_type="processor",
            metadata={"tag": "important"},
        )

        assert job.id == "custom-job"
        assert job.payload == {"key": "value"}
        assert job.priority == JobPriority.HIGH
        assert job.depends_on == ["dep1", "dep2"]
        assert job.max_retries == 5
        assert job.job_type == "processor"
        assert job.metadata == {"tag": "important"}

    def test_empty_id_raises(self):
        """Test that empty ID raises ValueError."""
        with pytest.raises(ValueError, match="Job ID cannot be empty"):
            DAGJob(id="")

    def test_negative_retries_raises(self):
        """Test that negative max_retries raises ValueError."""
        with pytest.raises(ValueError, match="max_retries must be non-negative"):
            DAGJob(id="test", max_retries=-1)


class TestDAGJobResult:
    """Test DAGJobResult dataclass."""

    def test_success_result(self):
        """Test successful job result."""
        start = datetime.utcnow()
        end = datetime.utcnow()

        result = DAGJobResult(
            job_id="test",
            success=True,
            result={"output": "data"},
            started_at=start,
            completed_at=end,
            retry_count=0,
        )

        assert result.job_id == "test"
        assert result.success is True
        assert result.result == {"output": "data"}
        assert result.error is None
        assert result.duration_seconds is not None
        assert result.duration_seconds >= 0

    def test_failure_result(self):
        """Test failed job result."""
        result = DAGJobResult(
            job_id="test",
            success=False,
            error="Something went wrong",
            retry_count=2,
        )

        assert result.success is False
        assert result.error == "Something went wrong"
        assert result.result is None


class TestDAGProgress:
    """Test DAGProgress dataclass."""

    def test_default_creation(self):
        """Test default progress creation."""
        progress = DAGProgress()

        assert progress.total_jobs == 0
        assert progress.completed_jobs == 0
        assert progress.failed_jobs == 0
        assert progress.running_jobs == 0
        assert progress.pending_jobs == 0
        assert progress.skipped_jobs == 0
        assert progress.percent_complete == 0.0
        assert progress.is_complete is True

    def test_percent_complete(self):
        """Test percentage calculation."""
        progress = DAGProgress(total_jobs=100, completed_jobs=50)
        assert progress.percent_complete == 50.0

        progress = DAGProgress(total_jobs=100, completed_jobs=25, failed_jobs=25)
        assert progress.percent_complete == 50.0

    def test_is_complete(self):
        """Test completion detection."""
        progress = DAGProgress(total_jobs=10, completed_jobs=5, failed_jobs=5)
        assert progress.is_complete is True

        progress = DAGProgress(total_jobs=10, completed_jobs=5, pending_jobs=5)
        assert progress.is_complete is False

    def test_duration_seconds(self):
        """Test duration calculation."""
        start = datetime.utcnow()
        time.sleep(0.01)
        end = datetime.utcnow()

        progress = DAGProgress(start_time=start, end_time=end)
        assert progress.duration_seconds is not None
        assert progress.duration_seconds > 0


class TestDAGResult:
    """Test DAGResult dataclass."""

    def test_all_succeeded(self):
        """Test all_succeeded property."""
        result = DAGResult(
            dag_id="test",
            status=DAGStatus.COMPLETED,
            job_results={
                "job1": DAGJobResult(job_id="job1", success=True),
                "job2": DAGJobResult(job_id="job2", success=True),
            },
        )
        assert result.all_succeeded is True

    def test_not_all_succeeded(self):
        """Test all_succeeded with failures."""
        result = DAGResult(
            dag_id="test",
            status=DAGStatus.FAILED,
            job_results={
                "job1": DAGJobResult(job_id="job1", success=True),
                "job2": DAGJobResult(job_id="job2", success=False, error="oops"),
            },
        )
        assert result.all_succeeded is False

    def test_failed_job_ids(self):
        """Test failed_job_ids property."""
        result = DAGResult(
            dag_id="test",
            status=DAGStatus.FAILED,
            job_results={
                "job1": DAGJobResult(job_id="job1", success=True),
                "job2": DAGJobResult(job_id="job2", success=False),
                "job3": DAGJobResult(job_id="job3", success=False),
            },
        )
        assert result.failed_job_ids == ["job2", "job3"]


class TestDAG:
    """Test DAG class."""

    def test_default_dag_id(self):
        """Test auto-generated DAG ID."""
        dag = DAG()
        assert dag.dag_id.startswith("dag_")

    def test_custom_dag_id(self):
        """Test custom DAG ID."""
        dag = DAG(dag_id="my-dag")
        assert dag.dag_id == "my-dag"

    def test_add_job(self):
        """Test adding a job."""
        dag = DAG()
        job = dag.add_job("job1", payload={"task": "test"})

        assert job.id == "job1"
        assert job.payload == {"task": "test"}
        assert "job1" in dag
        assert dag.job_count == 1

    def test_add_job_duplicate_raises(self):
        """Test adding duplicate job raises error."""
        dag = DAG()
        dag.add_job("job1")

        with pytest.raises(DAGError, match="already exists"):
            dag.add_job("job1")

    def test_add_dependency(self):
        """Test adding dependency between jobs."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_dependency("b", "a")

        assert dag.get_dependencies("b") == {"a"}
        assert dag.get_dependents("a") == {"b"}

    def test_add_dependency_missing_job_raises(self):
        """Test adding dependency to missing job raises error."""
        dag = DAG()
        dag.add_job("a")

        with pytest.raises(DAGNotFoundError, match="not found"):
            dag.add_dependency("b", "a")

    def test_add_dependency_missing_dep_raises(self):
        """Test adding dependency on missing job raises error."""
        dag = DAG()
        dag.add_job("a")

        with pytest.raises(DAGNotFoundError, match="not found"):
            dag.add_dependency("a", "b")

    def test_add_self_dependency_raises(self):
        """Test self-dependency raises error."""
        dag = DAG()
        dag.add_job("a")

        with pytest.raises(DAGError, match="cannot depend on itself"):
            dag.add_dependency("a", "a")

    def test_add_duplicate_dependency_raises(self):
        """Test adding duplicate dependency raises error."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])

        with pytest.raises(DAGError, match="already exists"):
            dag.add_dependency("b", "a")

    def test_remove_job(self):
        """Test removing a job."""
        dag = DAG()
        dag.add_job("a")
        dag.remove_job("a")

        assert "a" not in dag
        assert dag.job_count == 0

    def test_remove_job_with_dependents_raises(self):
        """Test removing job with dependents raises error."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])

        with pytest.raises(DAGError, match="has dependents"):
            dag.remove_job("a")

    def test_get_job(self):
        """Test getting a job."""
        dag = DAG()
        dag.add_job("a", payload={"key": "value"})

        job = dag.get_job("a")
        assert job.id == "a"
        assert job.payload == {"key": "value"}

    def test_get_job_not_found(self):
        """Test getting non-existent job raises error."""
        dag = DAG()

        with pytest.raises(DAGNotFoundError, match="not found"):
            dag.get_job("missing")

    def test_validate_no_cycle(self):
        """Test validation passes with no cycle."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["b"])

        dag.validate()  # Should not raise

    def test_validate_detects_cycle(self):
        """Test validation detects cycle."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c")
        dag.add_dependency("b", "a")
        dag.add_dependency("c", "b")
        dag.add_dependency("a", "c")  # Creates cycle: a -> b -> c -> a

        with pytest.raises(DAGCycleError, match="Cycle detected"):
            dag.validate()

    def test_validate_missing_dependency(self):
        """Test validation detects missing dependency."""
        dag = DAG()
        dag.add_job("a", depends_on=["missing"])

        with pytest.raises(DAGError, match="non-existent job"):
            dag.validate()

    def test_topological_sort_linear(self):
        """Test topological sort with linear dependencies."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["b"])

        levels = dag.topological_sort()
        assert levels == [["a"], ["b"], ["c"]]

    def test_topological_sort_parallel(self):
        """Test topological sort with parallel jobs."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c", depends_on=["a", "b"])

        levels = dag.topological_sort()
        # a and b can be in any order within first level
        assert set(levels[0]) == {"a", "b"}
        assert levels[1] == ["c"]

    def test_topological_sort_complex(self):
        """Test topological sort with complex dependencies."""
        dag = DAG()
        dag.add_job("root1")
        dag.add_job("root2")
        dag.add_job("mid1", depends_on=["root1"])
        dag.add_job("mid2", depends_on=["root1", "root2"])
        dag.add_job("leaf", depends_on=["mid1", "mid2"])

        levels = dag.topological_sort()
        assert set(levels[0]) == {"root1", "root2"}
        assert set(levels[1]) == {"mid1", "mid2"}
        assert levels[2] == ["leaf"]

    def test_topological_sort_cycle_raises(self):
        """Test topological sort with cycle raises error."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_dependency("a", "b")
        dag.add_dependency("b", "a")

        with pytest.raises(DAGCycleError):
            dag.topological_sort()

    def test_get_ready_jobs(self):
        """Test getting ready jobs."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["a"])
        dag.add_job("d", depends_on=["b", "c"])

        # Nothing completed, only 'a' is ready
        ready = dag.get_ready_jobs(set(), set())
        assert ready == ["a"]

        # After 'a' completes, 'b' and 'c' are ready
        ready = dag.get_ready_jobs({"a"}, set())
        assert set(ready) == {"b", "c"}

        # After all complete, nothing is ready
        ready = dag.get_ready_jobs({"a", "b", "c", "d"}, set())
        assert ready == []

    def test_get_jobs_with_failed_deps(self):
        """Test getting jobs with failed dependencies."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["a"])
        dag.add_job("d", depends_on=["b"])

        skipped = dag.get_jobs_with_failed_deps({"a"})
        assert set(skipped) == {"b", "c"}

        skipped = dag.get_jobs_with_failed_deps({"b"})
        assert skipped == ["d"]

    def test_jobs_property(self):
        """Test jobs property returns all jobs."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")

        jobs = dag.jobs
        assert len(jobs) == 2
        assert "a" in jobs
        assert "b" in jobs

    def test_len(self):
        """Test __len__ method."""
        dag = DAG()
        assert len(dag) == 0

        dag.add_job("a")
        assert len(dag) == 1

    def test_contains(self):
        """Test __contains__ method."""
        dag = DAG()
        dag.add_job("a")

        assert "a" in dag
        assert "b" not in dag


class TestDAGExecutor:
    """Test DAGExecutor class."""

    def test_basic_execution(self):
        """Test basic DAG execution."""
        dag = DAG()
        dag.add_job("a", payload={"value": 1})
        dag.add_job("b", payload={"value": 2})

        results = []

        def handler(job):
            results.append((job.id, job.payload["value"]))
            return job.payload["value"] * 2

        executor = DAGExecutor(max_workers=2)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert len(result.job_results) == 2
        assert result.job_results["a"].success is True
        assert result.job_results["a"].result == 2
        assert result.job_results["b"].result == 4

    def test_dependency_order(self):
        """Test jobs execute in dependency order."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["b"])

        execution_order = []

        def handler(job):
            execution_order.append(job.id)
            time.sleep(0.01)  # Small delay to ensure ordering
            return job.id

        executor = DAGExecutor(max_workers=1)  # Single worker to ensure order
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert execution_order == ["a", "b", "c"]

    def test_parallel_execution(self):
        """Test parallel job execution."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c")

        start_times = {}

        def handler(job):
            start_times[job.id] = time.time()
            time.sleep(0.1)
            return job.id

        executor = DAGExecutor(max_workers=3)
        start = time.time()
        result = executor.execute_dag(dag, handler)
        elapsed = time.time() - start

        assert result.status == DAGStatus.COMPLETED
        # All jobs should complete in ~0.1s, not ~0.3s
        assert elapsed < 0.25

    def test_job_failure(self):
        """Test handling of job failure."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")

        def handler(job):
            if job.id == "a":
                raise ValueError("Job a failed")
            return "success"

        executor = DAGExecutor(max_workers=2, continue_on_error=False)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.FAILED
        assert result.job_results["a"].success is False
        assert "failed" in result.job_results["a"].error.lower()

    def test_continue_on_error(self):
        """Test continue_on_error option."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c")

        def handler(job):
            if job.id == "a":
                raise ValueError("Job a failed")
            return "success"

        executor = DAGExecutor(max_workers=1, continue_on_error=True)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.FAILED
        assert result.job_results["a"].success is False
        assert result.job_results["b"].success is True
        assert result.job_results["c"].success is True

    def test_dependency_failure_skips_dependents(self):
        """Test that dependents are skipped when dependency fails."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c")  # Independent, should still run

        def handler(job):
            if job.id == "a":
                raise ValueError("Job a failed")
            return "success"

        executor = DAGExecutor(max_workers=2, continue_on_error=False)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.FAILED
        assert result.job_results["a"].success is False
        # b should be skipped due to failed dependency
        assert result.job_results["b"].success is False
        assert "skipped" in result.job_results["b"].error.lower()
        # c should still succeed
        assert result.job_results["c"].success is True

    def test_retry_logic(self):
        """Test job retry on failure."""
        dag = DAG()
        dag.add_job("a", max_retries=2)

        call_count = 0

        def handler(job):
            nonlocal call_count
            call_count += 1
            if call_count < 3:
                raise ValueError(f"Attempt {call_count} failed")
            return "success"

        executor = DAGExecutor(max_workers=1, retry_failed=True)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["a"].success is True
        assert call_count == 3  # Initial + 2 retries
        assert result.job_results["a"].retry_count == 2

    def test_max_retries_exceeded(self):
        """Test job fails after max retries."""
        dag = DAG()
        dag.add_job("a", max_retries=1)

        def handler(job):
            raise ValueError("Always fails")

        executor = DAGExecutor(max_workers=1, retry_failed=True)
        result = executor.execute_dag(dag, handler)

        assert result.status == DAGStatus.FAILED
        assert result.job_results["a"].success is False
        assert result.job_results["a"].retry_count == 1

    def test_progress_callback(self):
        """Test progress callbacks."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")

        progress_updates = []

        def handler(job):
            return job.id

        def on_progress(progress):
            progress_updates.append(
                (progress.completed_jobs, progress.running_jobs, progress.pending_jobs)
            )

        executor = DAGExecutor(max_workers=2)
        executor.on_progress(on_progress)
        executor.execute_dag(dag, handler)

        assert len(progress_updates) > 0
        # Final update should show all completed
        assert progress_updates[-1][0] == 2  # completed

    def test_cancel_execution(self):
        """Test cancelling execution."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c")

        def handler(job):
            time.sleep(0.5)
            return job.id

        executor = DAGExecutor(max_workers=1)

        # Start execution in a thread and cancel after short delay
        import threading

        def cancel_after_delay():
            time.sleep(0.1)
            executor.cancel()

        cancel_thread = threading.Thread(target=cancel_after_delay)
        cancel_thread.start()

        result = executor.execute_dag(dag, handler)
        cancel_thread.join()

        assert result.status == DAGStatus.CANCELLED

    def test_get_progress(self):
        """Test getting current progress."""
        dag = DAG()
        dag.add_job("a")

        def handler(job):
            return job.id

        executor = DAGExecutor(max_workers=1)
        executor.execute_dag(dag, handler)

        progress = executor.get_progress()
        assert progress.total_jobs == 1
        assert progress.completed_jobs == 1
        assert progress.is_complete is True


class TestDAGExecutorAsync:
    """Test async DAG execution."""

    @pytest.mark.asyncio
    async def test_async_execution(self):
        """Test basic async DAG execution."""
        dag = DAG()
        dag.add_job("a", payload={"value": 1})
        dag.add_job("b", payload={"value": 2})

        async def handler(job):
            await asyncio.sleep(0.01)
            return job.payload["value"] * 2

        executor = DAGExecutor(max_workers=2)
        result = await executor.execute_dag_async(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["a"].result == 2
        assert result.job_results["b"].result == 4

    @pytest.mark.asyncio
    async def test_async_sync_handler(self):
        """Test async execution with sync handler."""
        dag = DAG()
        dag.add_job("a")

        def sync_handler(job):
            return job.id

        executor = DAGExecutor(max_workers=1)
        result = await executor.execute_dag_async(dag, sync_handler)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["a"].result == "a"

    @pytest.mark.asyncio
    async def test_async_parallel_execution(self):
        """Test parallel async execution."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")
        dag.add_job("c")

        async def handler(job):
            await asyncio.sleep(0.1)
            return job.id

        executor = DAGExecutor(max_workers=3)
        start = time.time()
        result = await executor.execute_dag_async(dag, handler)
        elapsed = time.time() - start

        assert result.status == DAGStatus.COMPLETED
        assert elapsed < 0.25  # Should complete in ~0.1s, not ~0.3s

    @pytest.mark.asyncio
    async def test_async_dependency_order(self):
        """Test async jobs execute in dependency order."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b", depends_on=["a"])
        dag.add_job("c", depends_on=["b"])

        execution_order = []

        async def handler(job):
            execution_order.append(job.id)
            await asyncio.sleep(0.01)
            return job.id

        executor = DAGExecutor(max_workers=1)
        result = await executor.execute_dag_async(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert execution_order == ["a", "b", "c"]


class TestExecuteDagConvenience:
    """Test convenience functions."""

    def test_execute_dag_sync(self):
        """Test sync convenience function."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")

        def handler(job):
            return job.id.upper()

        result = execute_dag(dag, handler, max_workers=2)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["a"].result == "A"
        assert result.job_results["b"].result == "B"

    @pytest.mark.asyncio
    async def test_execute_dag_async(self):
        """Test async convenience function."""
        dag = DAG()
        dag.add_job("a")
        dag.add_job("b")

        async def handler(job):
            return job.id.upper()

        result = await execute_dag_async(dag, handler, max_workers=2)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["a"].result == "A"
        assert result.job_results["b"].result == "B"


class TestDAGIntegration:
    """Integration tests for DAG functionality."""

    def test_complex_workflow(self):
        """Test a complex workflow with multiple levels."""
        dag = DAG()

        # Level 0: Independent jobs
        dag.add_job("fetch-data", payload={"url": "https://api.example.com/data"})
        dag.add_job("fetch-config", payload={"url": "https://api.example.com/config"})

        # Level 1: Process after fetch
        dag.add_job(
            "process-data",
            depends_on=["fetch-data"],
            payload={"operation": "transform"},
        )
        dag.add_job(
            "validate-config",
            depends_on=["fetch-config"],
            payload={"operation": "validate"},
        )

        # Level 2: Combine results
        dag.add_job(
            "generate-report",
            depends_on=["process-data", "validate-config"],
            payload={"operation": "report"},
        )

        # Level 3: Final step
        dag.add_job(
            "send-notification",
            depends_on=["generate-report"],
            payload={"operation": "notify"},
        )

        execution_order = []

        def handler(job):
            execution_order.append(job.id)
            return f"completed-{job.id}"

        result = execute_dag(dag, handler, max_workers=4)

        assert result.status == DAGStatus.COMPLETED
        assert result.all_succeeded is True
        assert len(execution_order) == 6

        # Verify dependencies were respected
        assert execution_order.index("fetch-data") < execution_order.index("process-data")
        assert execution_order.index("fetch-config") < execution_order.index("validate-config")
        assert execution_order.index("process-data") < execution_order.index("generate-report")
        assert execution_order.index("validate-config") < execution_order.index("generate-report")
        assert execution_order.index("generate-report") < execution_order.index("send-notification")

    def test_data_passing_simulation(self):
        """Test simulating data passing between jobs via shared state."""
        dag = DAG()
        dag.add_job("step1")
        dag.add_job("step2", depends_on=["step1"])
        dag.add_job("step3", depends_on=["step2"])

        # Simulate shared state
        shared_data = {}

        def handler(job):
            if job.id == "step1":
                shared_data["value"] = 10
                return 10
            elif job.id == "step2":
                shared_data["value"] = shared_data.get("value", 0) * 2
                return shared_data["value"]
            elif job.id == "step3":
                shared_data["value"] = shared_data.get("value", 0) + 5
                return shared_data["value"]
            return 0

        result = execute_dag(dag, handler, max_workers=1)

        assert result.status == DAGStatus.COMPLETED
        assert result.job_results["step1"].result == 10
        assert result.job_results["step2"].result == 20
        assert result.job_results["step3"].result == 25

    def test_mixed_priorities(self):
        """Test DAG with mixed job priorities."""
        dag = DAG()
        dag.add_job("low", priority=JobPriority.LOW)
        dag.add_job("high", priority=JobPriority.HIGH)
        dag.add_job("critical", priority=JobPriority.CRITICAL)
        dag.add_job("normal", priority=JobPriority.NORMAL)

        def handler(job):
            return job.priority.value

        result = execute_dag(dag, handler, max_workers=4)

        assert result.status == DAGStatus.COMPLETED
        # Priority doesn't affect execution order in DAG executor,
        # but jobs should complete successfully
        assert result.job_results["critical"].result == JobPriority.CRITICAL.value
        assert result.job_results["high"].result == JobPriority.HIGH.value
        assert result.job_results["normal"].result == JobPriority.NORMAL.value
        assert result.job_results["low"].result == JobPriority.LOW.value

    def test_empty_dag(self):
        """Test executing an empty DAG."""
        dag = DAG()

        def handler(job):
            return job.id

        result = execute_dag(dag, handler)

        assert result.status == DAGStatus.COMPLETED
        assert result.progress.total_jobs == 0
        assert result.progress.is_complete is True
