from core.languages.python.cst_utils import parse_module_to_cst
from core.languages.python.testgen.postprocessing.topdef_terminator import delete_top_def_nodes


def test_delete_top_def_nodes() -> None:
    module_str = """def helper():
    pass

class MyClass:
    bob = your_uncle

def non_helper():
    pass

class ClassTypeHelper:
    def helper_method(self):
        pass
    def non_helper_method(self):
        pass

class ClassTypeNonHelper:
    def other_method(self):
        pass
"""

    deletable_list = ["module.helper", "module.ClassTypeHelper.helper_method", "module.MyClass"]
    expected = """def non_helper():
    pass

class ClassTypeNonHelper:
    def other_method(self):
        pass
"""
    result = delete_top_def_nodes(parse_module_to_cst(module_str), deletable_list).code
    assert result == expected


def test_delete_top_def_nodes_preserves_base_classes() -> None:
    """Test that classes used as base classes are not deleted even if in deletable_list.

    This tests the fix for the LayoutDumper bug where base classes were being deleted
    but their child classes were preserved, causing NameError at runtime.
    """
    module_str = """class LayoutDumper:
    layout_source: str = "unknown"
    def dump(self) -> dict:
        raise NotImplementedError()

class ObjectDetectionLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

class UnusedHelper:
    pass

def test_object_detection():
    dumper = ObjectDetectionLayoutDumper({})
    assert dumper.dump() == {}
"""

    # LayoutDumper is in the deletable list, but it should NOT be deleted
    # because ObjectDetectionLayoutDumper inherits from it
    deletable_list = ["module.LayoutDumper", "module.UnusedHelper"]

    expected = """class LayoutDumper:
    layout_source: str = "unknown"
    def dump(self) -> dict:
        raise NotImplementedError()

class ObjectDetectionLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

def test_object_detection():
    dumper = ObjectDetectionLayoutDumper({})
    assert dumper.dump() == {}
"""
    result = delete_top_def_nodes(parse_module_to_cst(module_str), deletable_list).code
    assert result == expected


def test_delete_top_def_nodes_deletes_unused_base_class() -> None:
    """Test that base classes CAN be deleted if no class inherits from them."""
    module_str = """class LayoutDumper:
    pass

class UnrelatedClass:
    pass

def test_something():
    pass
"""

    # LayoutDumper is in deletable list and nothing inherits from it, so it should be deleted
    deletable_list = ["module.LayoutDumper"]

    expected = """class UnrelatedClass:
    pass

def test_something():
    pass
"""
    result = delete_top_def_nodes(parse_module_to_cst(module_str), deletable_list).code
    assert result == expected


def test_delete_top_def_nodes_preserves_used_classes() -> None:
    """Test that classes directly instantiated in tests are preserved.

    This tests the fix where helper classes that are actually used in tests
    should NOT be deleted, even if they're in the deletable list.
    """
    module_str = """class LayoutDumper:
    layout_source: str = "unknown"
    def dump(self) -> dict:
        return {}

class ObjectDetectionLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

class ExtractedLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

class UnusedHelper:
    pass

def test_object_detection():
    dumper = ObjectDetectionLayoutDumper({})
    assert dumper.dump() == {}

def test_extracted():
    dumper = ExtractedLayoutDumper({"text": "hello"})
    assert dumper.dump() == {"text": "hello"}
"""

    # All these are in deletable list, but LayoutDumper, ObjectDetectionLayoutDumper,
    # and ExtractedLayoutDumper should be preserved because they're actually used
    deletable_list = [
        "module.LayoutDumper",
        "module.ObjectDetectionLayoutDumper",
        "module.ExtractedLayoutDumper",
        "module.UnusedHelper",
    ]

    expected = """class LayoutDumper:
    layout_source: str = "unknown"
    def dump(self) -> dict:
        return {}

class ObjectDetectionLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

class ExtractedLayoutDumper(LayoutDumper):
    def __init__(self, layout):
        self._layout = layout
    def dump(self) -> dict:
        return self._layout

def test_object_detection():
    dumper = ObjectDetectionLayoutDumper({})
    assert dumper.dump() == {}

def test_extracted():
    dumper = ExtractedLayoutDumper({"text": "hello"})
    assert dumper.dump() == {"text": "hello"}
"""
    result = delete_top_def_nodes(parse_module_to_cst(module_str), deletable_list).code
    assert result == expected


def test_delete_top_def_nodes_preserves_used_functions() -> None:
    """Test that helper functions actually called in tests are preserved."""
    module_str = """def helper_function(x):
    return x * 2

def unused_helper():
    return 42

def test_helper():
    result = helper_function(5)
    assert result == 10
"""

    deletable_list = ["module.helper_function", "module.unused_helper"]

    # helper_function should be preserved because it's called in test_helper
    # unused_helper should be deleted because nothing uses it
    expected = """def helper_function(x):
    return x * 2

def test_helper():
    result = helper_function(5)
    assert result == 10
"""
    result = delete_top_def_nodes(parse_module_to_cst(module_str), deletable_list).code
    assert result == expected
