"""Tests for AI-generated tree structure validation."""
import pytest

from app.core.ai_tree_validator import validate_generated_tree, count_tree_stats


def _make_valid_tree():
    """Helper: minimal valid tree for testing.

    Action nodes use next_node_id to point to a sibling (not children).
    The solution following an action is a sibling under the parent decision.
    """
    return {
        "id": "root",
        "type": "decision",
        "question": "Is the service running?",
        "options": [
            {"id": "opt-yes", "label": "Yes", "next_node_id": "check-logs"},
            {"id": "opt-no", "label": "No", "next_node_id": "restart-service"},
        ],
        "children": [
            {
                "id": "check-logs",
                "type": "decision",
                "question": "Are there errors in the logs?",
                "options": [
                    {"id": "opt-errors", "label": "Yes", "next_node_id": "fix-errors"},
                    {"id": "opt-clean", "label": "No", "next_node_id": "escalate"},
                ],
                "children": [
                    {
                        "id": "fix-errors",
                        "type": "solution",
                        "title": "Fix Errors",
                        "description": "Apply the fix for the errors found.",
                    },
                    {
                        "id": "escalate",
                        "type": "solution",
                        "title": "Escalate",
                        "description": "No errors found; escalate to Tier 2.",
                    },
                ],
            },
            {
                "id": "restart-service",
                "type": "action",
                "title": "Restart the Service",
                "description": "Restart the service and verify.",
                "commands": ["Restart-Service -Name 'TestService'"],
                "next_node_id": "service-resolved",
            },
            {
                "id": "service-resolved",
                "type": "solution",
                "title": "Service Restored",
                "description": "Service is running after restart.",
            },
        ],
    }


class TestValidTree:
    def test_valid_tree_passes(self):
        errors = validate_generated_tree(_make_valid_tree())
        assert errors == []

    def test_not_a_dict(self):
        errors = validate_generated_tree("not a dict")
        assert any("must be a JSON object" in e for e in errors)

    def test_root_not_decision(self):
        tree = _make_valid_tree()
        tree["type"] = "action"
        tree["title"] = "Fake"
        errors = validate_generated_tree(tree)
        assert any("Root node must be type 'decision'" in e for e in errors)


class TestNodeValidation:
    def test_missing_id(self):
        tree = _make_valid_tree()
        del tree["children"][0]["id"]
        errors = validate_generated_tree(tree)
        assert any("missing 'id'" in e for e in errors)

    def test_duplicate_ids(self):
        tree = _make_valid_tree()
        tree["children"][1]["id"] = "check-logs"  # same as sibling
        errors = validate_generated_tree(tree)
        assert any("Duplicate node ID" in e for e in errors)

    def test_invalid_node_type(self):
        tree = _make_valid_tree()
        tree["children"][0]["type"] = "unknown"
        errors = validate_generated_tree(tree)
        assert any("invalid type" in e for e in errors)

    def test_decision_missing_options(self):
        tree = _make_valid_tree()
        del tree["children"][0]["options"]
        errors = validate_generated_tree(tree)
        assert any("missing fields" in e for e in errors)

    def test_decision_less_than_2_options(self):
        tree = _make_valid_tree()
        tree["children"][0]["options"] = [
            {"id": "opt-1", "label": "Only", "next_node_id": "fix-errors"}
        ]
        errors = validate_generated_tree(tree)
        assert any("at least 2 options" in e for e in errors)

    def test_action_missing_next_node_id(self):
        tree = _make_valid_tree()
        del tree["children"][1]["next_node_id"]
        errors = validate_generated_tree(tree)
        assert any("missing 'next_node_id'" in e for e in errors)


class TestReferenceIntegrity:
    def test_option_references_nonexistent_child(self):
        tree = _make_valid_tree()
        tree["options"][0]["next_node_id"] = "nonexistent"
        errors = validate_generated_tree(tree)
        assert any("does not exist" in e for e in errors)

    def test_action_next_node_id_references_nonexistent_node(self):
        """Action next_node_id pointing to a node that doesn't exist anywhere in the tree."""
        tree = _make_valid_tree()
        tree["children"][1]["next_node_id"] = "ghost-node"
        errors = validate_generated_tree(tree)
        assert any("ghost-node" in e for e in errors)

    def test_duplicate_option_ids(self):
        tree = _make_valid_tree()
        tree["options"][0]["id"] = "same"
        tree["options"][1]["id"] = "same"
        errors = validate_generated_tree(tree)
        assert any("Duplicate option ID" in e for e in errors)


class TestGlobalChecks:
    def test_too_few_nodes(self):
        tree = {
            "id": "root",
            "type": "decision",
            "question": "Test?",
            "options": [
                {"id": "o1", "label": "A", "next_node_id": "s1"},
                {"id": "o2", "label": "B", "next_node_id": "s2"},
            ],
            "children": [
                {"id": "s1", "type": "solution", "title": "S1", "description": "D1"},
                {"id": "s2", "type": "solution", "title": "S2", "description": "D2"},
            ],
        }
        errors = validate_generated_tree(tree)
        assert any("Minimum 5 required" in e for e in errors)

    def test_too_few_solutions(self):
        tree = _make_valid_tree()
        # Remove all solutions except one — replace children of check-logs
        tree["children"][0]["children"] = [
            {
                "id": "only-solution",
                "type": "solution",
                "title": "Only",
                "description": "Only solution",
            }
        ]
        tree["children"][0]["options"] = [
            {"id": "o1", "label": "A", "next_node_id": "only-solution"},
            {"id": "o2", "label": "B", "next_node_id": "only-solution"},
        ]
        # Remove the solution that restart-service points to
        tree["children"].pop(2)  # remove service-resolved
        errors = validate_generated_tree(tree)
        assert any("solution" in e.lower() for e in errors)


class TestDeadEndDetection:
    def test_dead_end_decision_node(self):
        """A decision node with no children is a dead end."""
        tree = _make_valid_tree()
        # Remove children from check-logs decision node — becomes dead end
        tree["children"][0]["children"] = []
        errors = validate_generated_tree(tree)
        assert any("dead end" in e for e in errors)


class TestCrossReferenceSupport:
    def test_option_referencing_non_child_node_in_tree_is_valid(self):
        """A decision option can reference any node in the tree, not just direct children."""
        tree = _make_valid_tree()
        # Make root option point to a grandchild (not a direct child) — cross-reference
        tree["options"][0]["next_node_id"] = "fix-errors"  # grandchild of root
        errors = validate_generated_tree(tree)
        assert not any("non-existent child" in e for e in errors)
        assert not any("does not exist" in e for e in errors)

    def test_option_referencing_nonexistent_node_still_fails(self):
        """Cross-references must still point to nodes that exist in the tree."""
        tree = _make_valid_tree()
        tree["options"][0]["next_node_id"] = "totally-fake-id"
        errors = validate_generated_tree(tree)
        assert any("does not exist" in e for e in errors)

    def test_action_next_node_id_to_ancestor_is_valid(self):
        """Action node can loop back to an ancestor node."""
        tree = _make_valid_tree()
        tree["children"][1]["next_node_id"] = "root"
        errors = validate_generated_tree(tree)
        assert not any("does not exist" in e for e in errors)


class TestCountTreeStats:
    def test_stats_correct(self):
        tree = _make_valid_tree()
        stats = count_tree_stats(tree)
        assert stats["node_count"] == 6
        assert stats["decision_count"] == 2
        assert stats["action_count"] == 1
        assert stats["solution_count"] == 3
        assert stats["depth"] >= 3
