"""Tests for DAG editor API — CRUD operations on World DAG."""

from __future__ import annotations

import json

from orxaq.experiences.editor import DAGEditorAPI
from orxaq.orchestration.integration import OrxaqEngine


def _make_editor() -> DAGEditorAPI:
    engine = OrxaqEngine()
    engine.load_credit_ontology()
    return DAGEditorAPI(engine)


class TestListNodes:
    def test_list_returns_nodes(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/nodes", "GET")
        assert status == 200
        assert resp["count"] > 0
        assert "nodes" in resp
        node = resp["nodes"][0]
        assert "id" in node
        assert "name" in node

    def test_list_returns_all_fields(self):
        ed = _make_editor()
        resp, _ = ed.handle("/api/dag/nodes", "GET")
        node = resp["nodes"][0]
        assert "causal_role" in node
        assert "variable_type" in node


class TestListEdges:
    def test_list_returns_edges(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/edges", "GET")
        assert status == 200
        assert resp["count"] > 0
        edge = resp["edges"][0]
        assert "source" in edge
        assert "target" in edge

    def test_edges_have_metadata(self):
        ed = _make_editor()
        resp, _ = ed.handle("/api/dag/edges", "GET")
        edge = resp["edges"][0]
        assert "mechanism" in edge
        assert "confidence" in edge


class TestExport:
    def test_export_full(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/export", "GET")
        assert status == 200
        assert "nodes" in resp
        assert "edges" in resp
        assert resp["node_count"] == len(resp["nodes"])
        assert resp["edge_count"] == len(resp["edges"])


class TestAddNode:
    def test_add_new_node(self):
        ed = _make_editor()
        body = json.dumps({"id": "test_var", "name": "Test Variable"})
        resp, status = ed.handle("/api/dag/nodes", "POST", body)
        assert status == 201
        assert resp["node"] == "test_var"

        # Verify it exists
        nodes_resp, _ = ed.handle("/api/dag/nodes", "GET")
        ids = [n["id"] for n in nodes_resp["nodes"]]
        assert "test_var" in ids

    def test_add_duplicate_node(self):
        ed = _make_editor()
        body = json.dumps({"id": "test_dup", "name": "Dup"})
        ed.handle("/api/dag/nodes", "POST", body)
        resp, status = ed.handle("/api/dag/nodes", "POST", body)
        assert status == 409
        assert "already exists" in resp["error"]

    def test_add_node_missing_id(self):
        ed = _make_editor()
        body = json.dumps({"name": "No ID"})
        resp, status = ed.handle("/api/dag/nodes", "POST", body)
        assert status == 400
        assert "required" in resp["error"]

    def test_add_node_with_role(self):
        ed = _make_editor()
        body = json.dumps(
            {
                "id": "test_conf",
                "name": "Confounder",
                "causal_role": "confounder",
            }
        )
        resp, status = ed.handle("/api/dag/nodes", "POST", body)
        assert status == 201

    def test_add_node_invalid_role_uses_default(self):
        ed = _make_editor()
        body = json.dumps({"id": "test_bad", "causal_role": "invalid"})
        resp, status = ed.handle("/api/dag/nodes", "POST", body)
        assert status == 201


class TestAddEdge:
    def test_add_edge(self):
        ed = _make_editor()
        # First add two nodes
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "src_a"}))
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "tgt_b"}))
        body = json.dumps({"source": "src_a", "target": "tgt_b", "mechanism": "test"})
        resp, status = ed.handle("/api/dag/edges", "POST", body)
        assert status == 201
        assert "src_a" in resp["edge"]

    def test_add_edge_cycle_detection(self):
        ed = _make_editor()
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "cyc_a"}))
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "cyc_b"}))
        ed.handle(
            "/api/dag/edges",
            "POST",
            json.dumps({"source": "cyc_a", "target": "cyc_b"}),
        )
        # Reverse edge should fail (cycle)
        resp, status = ed.handle(
            "/api/dag/edges",
            "POST",
            json.dumps({"source": "cyc_b", "target": "cyc_a"}),
        )
        assert status == 409
        assert "Cycle" in resp["error"]

    def test_add_edge_missing_source(self):
        ed = _make_editor()
        body = json.dumps({"target": "x"})
        resp, status = ed.handle("/api/dag/edges", "POST", body)
        assert status == 400

    def test_add_edge_nonexistent_node(self):
        ed = _make_editor()
        body = json.dumps({"source": "nonexistent", "target": "also_nonexistent"})
        resp, status = ed.handle("/api/dag/edges", "POST", body)
        assert status == 400
        assert "not in DAG" in resp["error"]


class TestRemoveNode:
    def test_remove_existing_node(self):
        ed = _make_editor()
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "rm_node"}))
        resp, status = ed.handle(
            "/api/dag/nodes",
            "DELETE",
            json.dumps({"id": "rm_node"}),
        )
        assert status == 200
        assert resp["status"] == "deleted"

    def test_remove_nonexistent_node(self):
        ed = _make_editor()
        resp, status = ed.handle(
            "/api/dag/nodes",
            "DELETE",
            json.dumps({"id": "ghost"}),
        )
        assert status == 404

    def test_remove_node_cascades_edges(self):
        ed = _make_editor()
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "cas_a"}))
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "cas_b"}))
        ed.handle(
            "/api/dag/edges",
            "POST",
            json.dumps({"source": "cas_a", "target": "cas_b"}),
        )
        ed.handle("/api/dag/nodes", "DELETE", json.dumps({"id": "cas_a"}))

        # Edge should be gone
        edges_resp, _ = ed.handle("/api/dag/edges", "GET")
        sources = [e["source"] for e in edges_resp["edges"]]
        assert "cas_a" not in sources


class TestRemoveEdge:
    def test_remove_existing_edge(self):
        ed = _make_editor()
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "re_a"}))
        ed.handle("/api/dag/nodes", "POST", json.dumps({"id": "re_b"}))
        ed.handle(
            "/api/dag/edges",
            "POST",
            json.dumps({"source": "re_a", "target": "re_b"}),
        )
        resp, status = ed.handle(
            "/api/dag/edges",
            "DELETE",
            json.dumps({"source": "re_a", "target": "re_b"}),
        )
        assert status == 200
        assert resp["status"] == "deleted"

    def test_remove_nonexistent_edge(self):
        ed = _make_editor()
        resp, status = ed.handle(
            "/api/dag/edges",
            "DELETE",
            json.dumps({"source": "no_a", "target": "no_b"}),
        )
        assert status == 404


class TestEdgeRouting:
    def test_invalid_path(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/unknown", "GET")
        assert status == 404

    def test_invalid_json(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/nodes", "POST", "not json")
        assert status == 400
        assert "invalid_json" in resp["error"]

    def test_missing_body(self):
        ed = _make_editor()
        resp, status = ed.handle("/api/dag/nodes", "POST")
        assert status == 400


class TestServerEditorRoutes:
    """Test the server integration with editor routes."""

    def test_get_editor_page(self):
        """Editor HTML page is serveable."""
        from pathlib import Path

        static = Path(__file__).parent.parent.parent / "src" / "orxaq" / "experiences" / "static"
        assert (static / "editor.html").exists()

    def test_editor_api_via_server(self):
        """The server correctly binds the editor API."""
        from orxaq.experiences.api import OrxaqAPI
        from orxaq.experiences.editor import DAGEditorAPI

        api = OrxaqAPI()
        editor = DAGEditorAPI(api.engine)
        resp, status = editor.handle("/api/dag/nodes", "GET")
        assert status == 200
        assert resp["count"] > 0
