from __future__ import annotations

import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from planner.dag_parser import DAGParser


def make_step(step_id: str, action: str, dependencies=None, params=None) -> dict:
    step = {
        "step_id": step_id,
        "type": "EXECUTION",
        "agent_role": "robot",
        "action": action,
    }
    if dependencies is not None:
        step["dependencies"] = dependencies
    if params is not None:
        step["params"] = params
    return step


def make_plan(steps, intent="test intent", topology="DAG") -> dict:
    return {
        "intent": intent,
        "workflow_topology": topology,
        "plan_id": "plan_test_001",
        "steps": steps,
    }


def test_parse_invalid_json():
    parser = DAGParser()
    result = parser.parse("{not valid json")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Invalid JSON."


def test_parse_missing_required_field():
    parser = DAGParser()
    plan = {
        "intent": "do something",
        "workflow_topology": "DAG",
        "plan_id": "plan_missing_steps",
    }
    result = parser.parse(plan, expected_intent="do something")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Schema validation failed."
    assert "steps" in (result.error.details or "")


def test_parse_intent_mismatch():
    parser = DAGParser()
    plan = make_plan([make_step("s1", "WAIT")], intent="original intent")
    result = parser.parse(plan, expected_intent="different intent")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Plan intent does not match user intent."


def test_parse_empty_steps():
    parser = DAGParser()
    plan = make_plan([], intent="empty steps")
    result = parser.parse(plan, expected_intent="empty steps")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Plan has no steps."


def test_parse_duplicate_step_ids():
    parser = DAGParser()
    steps = [make_step("s1", "WAIT"), make_step("s1", "WAIT")]
    plan = make_plan(steps, intent="duplicate ids")
    result = parser.parse(plan, expected_intent="duplicate ids")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Duplicate step_id values found."
    assert "s1" in (result.error.details or "")


def test_parse_unknown_dependency():
    parser = DAGParser()
    steps = [
        make_step("s1", "WAIT"),
        make_step("s2", "WAIT", dependencies=["missing"]),
    ]
    plan = make_plan(steps, intent="unknown dependency")
    result = parser.parse(plan, expected_intent="unknown dependency")
    assert not result.ok
    assert result.error is not None
    assert result.error.message == "Dependency refers to unknown step_id."
    assert "missing" in (result.error.details or "")


def test_sequential_autofills_dependencies():
    parser = DAGParser()
    steps = [
        make_step("s1", "CHECK_BATTERY"),
        make_step("s2", "NAVIGATE"),
        make_step("s3", "WAIT"),
    ]
    plan = make_plan(steps, intent="sequential", topology="SEQUENTIAL")
    result = parser.parse(plan, expected_intent="sequential")
    assert result.ok
    assert result.plan_graph is not None
    assert result.plan_graph.step_lookup["s2"]["dependencies"] == ["s1"]
    assert result.plan_graph.step_lookup["s3"]["dependencies"] == ["s2"]
    assert set(result.plan_graph.entry_steps) == {"s1"}


def test_parse_builds_dependency_graph():
    parser = DAGParser()
    steps = [
        make_step("s1", "CHECK_BATTERY"),
        make_step("s2", "NAVIGATE", dependencies=["s1"]),
        make_step("s3", "SPEAK", dependencies=["s2"]),
    ]
    plan = make_plan(steps, intent="graph")
    result = parser.parse(plan, expected_intent="graph")
    assert result.ok
    plan_graph = result.plan_graph
    assert plan_graph is not None
    assert plan_graph.dependencies["s2"] == {"s1"}
    assert plan_graph.children["s1"] == {"s2"}
    assert plan_graph.children["s2"] == {"s3"}
