"""Tests for call graph core logic covering edge cases."""

from pathlib import Path
from tempfile import NamedTemporaryFile

from vibelexity.call_graph.core import build_call_graph
from vibelexity.parsers.python import parse


def test_build_call_graph_unmatched_function():
    """Calls to undefined functions get marked as external."""
    code = """
def foo():
    undefined_function()
    bar()

def bar():
    pass
"""
    with NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        filepath = f.name

    try:
        graph = build_call_graph([Path(filepath)])
        external_calls = [c for c in graph.calls if c.call_type == "external"]
        assert len(external_calls) >= 1
        external_call_names = {c.callee_function for c in external_calls}
        assert "undefined_function" in external_call_names
    finally:
        Path(filepath).unlink()


def test_build_call_graph_same_file_priority():
    """Prioritize same-file functions over cross-file."""
    code1 = """
def foo():
    helper()

def helper():
    pass
"""
    code2 = """
def helper():
    pass

def bar():
    helper()
"""
    with (
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f1,
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f2,
    ):
        f1.write(code1)
        f2.write(code2)
        filepath1 = f1.name
        filepath2 = f2.name

    try:
        graph = build_call_graph([Path(filepath1), Path(filepath2)])
        foo_calls = [c for c in graph.calls if c.caller_function == "foo"]
        assert len(foo_calls) == 1
        assert foo_calls[0].callee_file == filepath1
    finally:
        Path(filepath1).unlink()
        Path(filepath2).unlink()


def test_build_call_graph_cross_file_call():
    """Cross-file calls properly resolved."""
    code1 = """
def foo():
    bar()
"""
    code2 = """
def bar():
    pass
"""
    with (
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f1,
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f2,
    ):
        f1.write(code1)
        f2.write(code2)
        filepath1 = f1.name
        filepath2 = f2.name

    try:
        graph = build_call_graph([Path(filepath1), Path(filepath2)])
        foo_calls = [c for c in graph.calls if c.caller_function == "foo"]
        assert len(foo_calls) == 1
        assert foo_calls[0].callee_file == filepath2
    finally:
        Path(filepath1).unlink()
        Path(filepath2).unlink()


def test_build_call_graph_empty_files():
    """Empty source files are handled."""
    with NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write("")
        filepath = f.name

    try:
        graph = build_call_graph([Path(filepath)])
        assert len(graph.functions) == 0
        assert len(graph.calls) == 0
    finally:
        Path(filepath).unlink()


def test_find_call_cycles_with_external():
    """External calls don't interfere with cycle detection."""
    from vibelexity.call_graph.core import find_call_cycles

    code = """
def foo():
    print('hello')
    bar()

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

    try:
        graph = build_call_graph([Path(filepath)])
        cycles = find_call_cycles(graph)
        assert len(cycles) > 0
    finally:
        Path(filepath).unlink()


def test_find_unused_functions_ignores_dunder():
    """Dunder methods are considered used."""
    from vibelexity.call_graph.core import find_unused_functions

    code = """
class MyClass:
    def __init__(self):
        self.x = 1

    def __str__(self):
        return f"x={self.x}"
"""
    with NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        filepath = f.name

    try:
        graph = build_call_graph([Path(filepath)])
        unused = find_unused_functions(graph)
        dunder_methods = [
            f for f in unused if f.name.startswith("__") and f.name.endswith("__")
        ]
        assert len(dunder_methods) == 0
    finally:
        Path(filepath).unlink()


def test_build_call_graph_multiple_def_names():
    """Handles multiple functions with same name in different files."""
    code1 = """
def helper():
    return 1
"""
    code2 = """
def helper():
    return 2

def foo():
    helper()
"""
    with (
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f1,
        NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f2,
    ):
        f1.write(code1)
        f2.write(code2)
        filepath1 = f1.name
        filepath2 = f2.name

    try:
        graph = build_call_graph([Path(filepath1), Path(filepath2)])
        foo_calls = [c for c in graph.calls if c.caller_function == "foo"]
        assert len(foo_calls) == 1
        assert foo_calls[0].callee_file == filepath2
    finally:
        Path(filepath1).unlink()
        Path(filepath2).unlink()


def test_build_call_graph_none_callee_file():
    """Handles calls where callee_file is None (external calls)."""
    code = """
def foo():
    print('hi')
"""
    with NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
        f.write(code)
        filepath = f.name

    try:
        graph = build_call_graph([Path(filepath)])
        calls = [c for c in graph.calls if c.caller_function == "foo"]
        assert len(calls) == 1
        assert calls[0].call_type == "external"
    finally:
        Path(filepath).unlink()
