"""
Tests for Phase 35: Workflow Engine.
"""

import json
import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    workflows = tmp_path / "workflows"
    defs = workflows / "definitions"
    runs = workflows / "runs"
    for d in (workflows, defs, runs):
        d.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.workflow as w
    monkeypatch.setattr(w, "WORKFLOWS_DIR", workflows)
    monkeypatch.setattr(w, "DEFINITIONS_DIR", defs)
    monkeypatch.setattr(w, "RUNS_DIR", runs)


def _sample_steps():
    return [
        {"id": "plan", "type": "task", "title": "Plan the work", "depends_on": []},
        {"id": "build", "type": "task", "title": "Build it", "depends_on": ["plan"]},
        {"id": "test", "type": "task", "title": "Test it", "depends_on": ["build"]},
        {"id": "deploy", "type": "task", "title": "Deploy it", "depends_on": ["test"]},
    ]


class TestWorkflowDefinition:
    def test_create(self):
        from brynq.workflow import create_workflow
        wf = create_workflow("CI Pipeline", "Build, test, deploy", _sample_steps())
        assert wf["workflow_id"]
        assert wf["name"] == "CI Pipeline"
        assert len(wf["steps"]) == 4

    def test_create_no_name(self):
        from brynq.workflow import create_workflow
        result = create_workflow("")
        assert "error" in result

    def test_duplicate_step_id(self):
        from brynq.workflow import create_workflow
        steps = [{"id": "dup", "type": "task"}, {"id": "dup", "type": "task"}]
        result = create_workflow("Bad", steps=steps)
        assert "error" in result

    def test_invalid_step_type(self):
        from brynq.workflow import create_workflow
        steps = [{"id": "s1", "type": "magic"}]
        result = create_workflow("Bad", steps=steps)
        assert "error" in result

    def test_unknown_dependency(self):
        from brynq.workflow import create_workflow
        steps = [{"id": "s1", "type": "task", "depends_on": ["nonexistent"]}]
        result = create_workflow("Bad", steps=steps)
        assert "error" in result

    def test_get_workflow(self):
        from brynq.workflow import create_workflow, get_workflow
        wf = create_workflow("Test", steps=_sample_steps())
        result = get_workflow(wf["workflow_id"])
        assert result["name"] == "Test"

    def test_list_workflows(self):
        from brynq.workflow import create_workflow, list_workflows
        create_workflow("A")
        create_workflow("B")
        assert len(list_workflows()) == 2

    def test_delete_workflow(self):
        from brynq.workflow import create_workflow, delete_workflow, get_workflow
        wf = create_workflow("Delete me")
        assert delete_workflow(wf["workflow_id"]) is True
        assert get_workflow(wf["workflow_id"]) is None

    def test_delete_nonexistent(self):
        from brynq.workflow import delete_workflow
        assert delete_workflow("nope") is False


class TestWorkflowExecution:
    def test_start_workflow(self):
        from brynq.workflow import create_workflow, start_workflow
        wf = create_workflow("Run me", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        assert run["status"] == "running"
        assert run["step_statuses"]["plan"]["status"] == "ready"
        assert run["step_statuses"]["build"]["status"] == "pending"

    def test_start_nonexistent(self):
        from brynq.workflow import start_workflow
        result = start_workflow("nope")
        assert "error" in result

    def test_complete_step_advances(self):
        from brynq.workflow import create_workflow, start_workflow, complete_step
        wf = create_workflow("Advance", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        result = complete_step(run["run_id"], "plan", "Planning done")
        assert result["steps_completed"] == 1
        assert "build" in result["newly_ready"]

    def test_full_workflow_lifecycle(self):
        from brynq.workflow import create_workflow, start_workflow, complete_step, get_run
        wf = create_workflow("Full", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        rid = run["run_id"]
        complete_step(rid, "plan")
        complete_step(rid, "build")
        complete_step(rid, "test")
        result = complete_step(rid, "deploy")
        assert result["workflow_status"] == "completed"
        final = get_run(rid)
        assert final["status"] == "completed"
        assert final["completed_at"] is not None

    def test_fail_step(self):
        from brynq.workflow import create_workflow, start_workflow, fail_step, get_run
        wf = create_workflow("Fail", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        fail_step(run["run_id"], "plan", "Syntax error")
        final = get_run(run["run_id"])
        assert final["status"] == "failed"

    def test_skip_step(self):
        from brynq.workflow import create_workflow, start_workflow, skip_step, complete_step
        steps = [
            {"id": "check", "type": "condition", "title": "Check env"},
            {"id": "deploy", "type": "task", "title": "Deploy", "depends_on": ["check"]},
        ]
        wf = create_workflow("Skip", steps=steps)
        run = start_workflow(wf["workflow_id"])
        skip_step(run["run_id"], "check", "Not needed")
        from brynq.workflow import get_run
        r = get_run(run["run_id"])
        assert r["step_statuses"]["deploy"]["status"] == "ready"

    def test_cancel_run(self):
        from brynq.workflow import create_workflow, start_workflow, cancel_run, get_run
        wf = create_workflow("Cancel", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        cancel_run(run["run_id"])
        final = get_run(run["run_id"])
        assert final["status"] == "cancelled"

    def test_parallel_steps(self):
        from brynq.workflow import create_workflow, start_workflow, complete_step
        steps = [
            {"id": "plan", "type": "task", "title": "Plan"},
            {"id": "build_a", "type": "task", "title": "Build A", "depends_on": ["plan"]},
            {"id": "build_b", "type": "task", "title": "Build B", "depends_on": ["plan"]},
            {"id": "merge", "type": "task", "title": "Merge", "depends_on": ["build_a", "build_b"]},
        ]
        wf = create_workflow("Parallel", steps=steps)
        run = start_workflow(wf["workflow_id"])
        result = complete_step(run["run_id"], "plan")
        # Both build_a and build_b should be ready (parallel)
        assert "build_a" in result["newly_ready"]
        assert "build_b" in result["newly_ready"]
        # Complete one — merge still pending
        complete_step(run["run_id"], "build_a")
        from brynq.workflow import get_run
        r = get_run(run["run_id"])
        assert r["step_statuses"]["merge"]["status"] == "pending"
        # Complete other — merge ready
        result2 = complete_step(run["run_id"], "build_b")
        assert "merge" in result2["newly_ready"]


class TestQueries:
    def test_get_ready_steps(self):
        from brynq.workflow import create_workflow, start_workflow, get_ready_steps
        wf = create_workflow("Ready", steps=_sample_steps())
        run = start_workflow(wf["workflow_id"])
        ready = get_ready_steps(run["run_id"])
        assert ready == ["plan"]

    def test_list_runs(self):
        from brynq.workflow import create_workflow, start_workflow, list_runs
        wf = create_workflow("List", steps=_sample_steps())
        start_workflow(wf["workflow_id"])
        start_workflow(wf["workflow_id"])
        assert len(list_runs()) == 2

    def test_list_runs_filter(self):
        from brynq.workflow import create_workflow, start_workflow, cancel_run, list_runs
        wf = create_workflow("Filter", steps=_sample_steps())
        r1 = start_workflow(wf["workflow_id"])
        start_workflow(wf["workflow_id"])
        cancel_run(r1["run_id"])
        cancelled = list_runs(status="cancelled")
        assert len(cancelled) == 1

    def test_stats(self):
        from brynq.workflow import create_workflow, start_workflow, complete_step, get_workflow_stats
        steps = [{"id": "s1", "type": "task", "title": "Only step"}]
        wf = create_workflow("Stats", steps=steps)
        run = start_workflow(wf["workflow_id"])
        complete_step(run["run_id"], "s1")
        stats = get_workflow_stats()
        assert stats["total_workflows"] == 1
        assert stats["completed_runs"] == 1
