import pandas as pd
import pytest

from private_gpt.components.postprocessor.tree_expansion.split_subtrees import (
    SplitSubtreeAlg,
)
from private_gpt.components.readers.nodes import (
    DocumentRootNode,
    ListNode,
    SectionNode,
    TableNode,
    TextNode,
)


def create_multilevel_sections() -> DocumentRootNode:
    """Creates a tree with multiple nested section levels."""
    root = DocumentRootNode()

    # First main section with subsections
    section1 = SectionNode(text="Main Section 1")
    root.add_child(section1)
    text1 = TextNode(text="Main section 1 content")
    section1.add_child(text1)

    # Subsections under section1
    subsection1_1 = SectionNode(text="Subsection 1.1")
    section1.add_child(subsection1_1)
    text1_1 = TextNode(text="Subsection 1.1 content")
    subsection1_1.add_child(text1_1)

    subsection1_2 = SectionNode(text="Subsection 1.2")
    section1.add_child(subsection1_2)
    text1_2 = TextNode(text="Subsection 1.2 content")
    subsection1_2.add_child(text1_2)

    # Second main section with deeper nesting
    section2 = SectionNode(text="Main Section 2")
    root.add_child(section2)

    subsection2_1 = SectionNode(text="Subsection 2.1")
    section2.add_child(subsection2_1)

    # Even deeper nesting
    subsubsection2_1_1 = SectionNode(text="SubSubsection 2.1.1")
    subsection2_1.add_child(subsubsection2_1_1)
    text2_1_1 = TextNode(text="Deep nested content")
    subsubsection2_1_1.add_child(text2_1_1)

    # Update references
    root.update_references()

    return root


def create_no_sections() -> DocumentRootNode:
    """Creates a tree with no sections, just different types of content."""
    root = DocumentRootNode()

    # Add some text nodes
    text1 = TextNode(text="First paragraph")
    root.add_child(text1)

    text2 = TextNode(text="Second paragraph")
    root.add_child(text2)

    # Add a list
    list_node = ListNode()
    root.add_child(list_node)
    list_item1 = TextNode(text="List item 1")
    list_item2 = TextNode(text="List item 2")
    list_node.add_child(list_item1)
    list_node.add_child(list_item2)

    # Add a table
    df = pd.DataFrame({"Column1": ["A", "B"], "Column2": [1, 2]})
    table = TableNode(df=df, description="Sample table")
    root.add_child(table)

    # Update references
    root.update_references()

    return root


def create_multiple_sections_same_level() -> DocumentRootNode:
    """Creates a tree with multiple sections at the same level."""
    root = DocumentRootNode()

    # Section 1 with content
    section1 = SectionNode(text="Section 1")
    root.add_child(section1)
    text1 = TextNode(text="Content for section 1")
    section1.add_child(text1)

    # Section 2 with list
    section2 = SectionNode(text="Section 2")
    root.add_child(section2)
    list_node = ListNode()
    section2.add_child(list_node)
    list_item = TextNode(text="List item in section 2")
    list_node.add_child(list_item)

    # Section 3 with table
    section3 = SectionNode(text="Section 3")
    root.add_child(section3)
    df = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
    table = TableNode(df=df, description="Table in section 3")
    section3.add_child(table)

    # Update references
    root.update_references()

    return root


def create_mixed_depth_sections() -> DocumentRootNode:
    """Creates a tree with sections at different depths mixed together."""
    root = DocumentRootNode()

    # First section with deep nesting
    section1 = SectionNode(text="Section 1")
    root.add_child(section1)

    subsection1_1 = SectionNode(text="Subsection 1.1")
    section1.add_child(subsection1_1)

    subsubsection1_1_1 = SectionNode(text="SubSubsection 1.1.1")
    subsection1_1.add_child(subsubsection1_1_1)
    text1 = TextNode(text="Deep nested content")
    subsubsection1_1_1.add_child(text1)

    # Second section at root level
    section2 = SectionNode(text="Section 2")
    root.add_child(section2)
    text2 = TextNode(text="Root level content")
    section2.add_child(text2)

    # Third section with medium nesting
    section3 = SectionNode(text="Section 3")
    root.add_child(section3)

    subsection3_1 = SectionNode(text="Subsection 3.1")
    section3.add_child(subsection3_1)
    text3 = TextNode(text="Medium nested content")
    subsection3_1.add_child(text3)

    # Update references
    root.update_references()

    return root


@pytest.mark.parametrize(
    ("tree_setup", "expected_min_subtrees", "description"),
    [
        (create_no_sections, 1, "No sections"),
        (create_multiple_sections_same_level, 3, "Multiple sections at same level"),
        (create_mixed_depth_sections, 3, "Mixed depth sections"),
        (create_multilevel_sections, 3, "Multilevel nested sections"),
    ],
)
def test_various_tree_structures(
    tree_setup,
    expected_min_subtrees: int,
    description: str,
) -> None:
    """Test the splitter with various tree structures."""
    alg = SplitSubtreeAlg()
    tree = tree_setup()
    tree.print_tree()  # For debugging
    subtrees = alg.split_subtree(tree)

    print(f"\nTesting: {description}")
    print(f"Generated {len(subtrees)} subtrees")
    for i, st in enumerate(subtrees):
        print(f"\nSubtree {i}:")
        st.print_tree()

    # Basic quantity check
    assert len(subtrees) >= expected_min_subtrees, (
        f"Expected at least {expected_min_subtrees} subtrees for {description}, "
        f"but got {len(subtrees)}"
    )

    # Verify each subtree is valid
    for subtree in subtrees:
        assert isinstance(
            subtree, DocumentRootNode
        ), "Subtree root should be DocumentRoot"
        # Check that each subtree has at least one child
        assert len(subtree.children) > 0, "Subtree should not be empty"


def test_multilevel_section_hierarchy() -> None:
    """Detailed test for multilevel section hierarchy preservation."""
    alg = SplitSubtreeAlg()
    tree = create_multilevel_sections()
    tree.print_tree()  # For debugging
    subtrees = alg.split_subtree(tree)

    print("\nTesting: Multilevel section hierarchy")
    print(f"Generated {len(subtrees)} subtrees")
    for i, st in enumerate(subtrees):
        print(f"\nSubtree {i}:")
        st.print_tree()

    # Verify that deep nested sections stay together
    for subtree in subtrees:
        if len(subtree.children) > 0 and isinstance(subtree.children[0], SectionNode):
            section = subtree.children[0]
            # If this section has subsections, verify the parent-child relationship
            if any(isinstance(child, SectionNode) for child in section.children):
                # Verify parent references are correct
                for child in section.children:
                    if isinstance(child, SectionNode):
                        assert (
                            child.parent.id_ == section.id_
                        ), "Parent-child relationship broken"


def test_no_sections_integrity() -> None:
    """Detailed test for trees without sections."""
    alg = SplitSubtreeAlg()
    tree = create_no_sections()
    tree.print_tree()  # For debugging
    subtrees = alg.split_subtree(tree)

    print("\nTesting: No sections")
    print(f"Generated {len(subtrees)} subtrees")
    for i, st in enumerate(subtrees):
        print(f"\nSubtree {i}:")
        st.print_tree()

    # Should return exactly one subtree as there are no section splits
    assert len(subtrees) == 1, "Tree without sections should not be split"

    # Verify all content is preserved
    original_nodes = list(tree.flatten())
    result_nodes = list(subtrees[0].flatten())
    assert len(result_nodes) >= len(
        original_nodes
    ), "Content was lost in no-sections tree"

    # Verify the types of nodes are preserved
    original_types = [type(n) for n in original_nodes]
    result_types = [type(n) for n in result_nodes]
    result_types = result_types[len(result_types) - len(original_types) :]

    assert original_types == result_types, "Node types changed in no-sections tree"


def test_multiple_sections_relationships() -> None:
    """Detailed test for multiple sections at the same level."""
    alg = SplitSubtreeAlg()
    tree = create_multiple_sections_same_level()
    tree.print_tree()  # For debugging
    subtrees = alg.split_subtree(tree)

    print("\nTesting: Multiple sections at same level")
    print(f"Generated {len(subtrees)} subtrees")
    for i, st in enumerate(subtrees):
        print(f"\nSubtree {i}:")
        st.print_tree()

    # Verify each section is in its own subtree
    section_counts = [
        sum(1 for child in subtree.flatten() if isinstance(child, SectionNode))
        for subtree in subtrees
    ]

    # Each subtree should have exactly one section
    assert all(
        count == 1 for count in section_counts
    ), "Each subtree should contain exactly one section"

    # Verify content type preservation
    for subtree in subtrees:
        section = next(
            (child for child in subtree.children if isinstance(child, SectionNode)),
            None,
        )
        if section:
            # Verify that the section's content is preserved
            if "Section 2" in section.text:
                assert any(
                    isinstance(node, ListNode) for node in section.flatten()
                ), "List content not preserved in Section 2"
            elif "Section 3" in section.text:
                assert any(
                    isinstance(node, TableNode) for node in section.flatten()
                ), "Table content not preserved in Section 3"
