"""Tests for unused function detection."""

from __future__ import annotations

from pathlib import Path
import tempfile

from vibelexity.call_graph import build_call_graph, find_unused_functions


def test_entry_point_unused():
    code = """
def calling():
    called()

def called():
    pass
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # 'calling' is an entry point that's never called, so it's unused
        # 'called' is called by 'calling', so it's not unused
        unused_names = {u.name for u in unused}
        assert "calling" in unused_names
        assert "called" not in unused_names

        Path(f.name).unlink()


def test_mutual_cycle_both_used():
    code = """
def foo():
    return bar()

def bar():
    return foo()
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # In a mutual call cycle, both functions are called (by each other)
        # So neither should be detected as unused
        assert len(unused) == 0

        Path(f.name).unlink()


def test_private_filtered():
    code = """
def _helper():
    pass

def main():
    _helper()
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # 'main' is an entry point (never called), so it's unused
        # '_helper' is called by main, but also filtered because it starts with '_'
        # So only 'main' appears in unused
        unused_names = {u.name for u in unused}
        assert len(unused_names) == 1
        assert "main" in unused_names
        assert "_helper" not in unused_names

        Path(f.name).unlink()


def test_chained_usage():
    code = """
def a():
    b()

def b():
    c()

def c():
    pass
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # 'a' is the entry point, not called by anything
        # 'b' and 'c' are called by a and b respectively, so not unused
        unused_names = {u.name for u in unused}
        assert "a" in unused_names
        assert "b" not in unused_names
        assert "c" not in unused_names

        Path(f.name).unlink()


def test_unused_function_detection():
    code = """
def a():
    pass

def b():
    pass
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # Neither function is called, so both are unused
        unused_names = {u.name for u in unused}
        assert len(unused_names) == 2
        assert "a" in unused_names
        assert "b" in unused_names

        Path(f.name).unlink()


def test_caller_is_unused():
    code = """
def foo():
    return bar()

def bar():
    return 42
"""
    with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        f.flush()

        graph = build_call_graph([Path(f.name)])
        unused = find_unused_functions(graph)

        # 'foo' is not called by anyone, so it's unused
        # 'bar' IS called by 'foo', so it's not unused
        unused_names = {u.name for u in unused}
        assert "foo" in unused_names
        assert "bar" not in unused_names

        Path(f.name).unlink()
