"""Tests for fatness metrics."""

import os

from vibelexity.metrics.fatness import FatnessReport, compute_fatness
from vibelexity.models import (
    CallEdge,
    CallGraph,
    FileComplexity,
    FunctionComplexity,
    FunctionDefinition,
)


def test_empty_inputs():
    """Empty file complexities returns empty report."""
    report = compute_fatness([])
    assert report.fat_files == []
    assert report.fat_functions == []


def test_single_small_file():
    """Small file not exceeding any thresholds."""
    file = FileComplexity(
        path="/path/to/file.py",
        loc=100,
        functions=[
            FunctionComplexity(
                name="small_func",
                line=1,
                complexity=1,
                func_loc=10,
                param_count=2,
            )
        ],
    )
    report = compute_fatness([file])
    assert len(report.fat_files) == 0
    assert len(report.fat_functions) == 0


def test_fat_by_loc_threshold():
    """File exceeding 500 LOC is marked as fat."""
    file = FileComplexity(
        path="/path/to/big_file.py",
        loc=501,
        functions=[],
    )
    report = compute_fatness([file])
    assert len(report.fat_files) == 1
    assert report.fat_files[0]["path"] == "/path/to/big_file.py"
    assert report.fat_files[0]["loc"] == 501
    assert "501 LOC" in report.fat_files[0]["reason"]


def test_fat_by_function_count_threshold():
    """File with 21+ functions is marked as fat."""
    file = FileComplexity(
        path="/path/to/many_funcs.py",
        loc=200,
        functions=[
            FunctionComplexity(
                name=f"func_{i}", line=1, complexity=1, func_loc=5, param_count=0
            )
            for i in range(21)
        ],
    )
    report = compute_fatness([file])
    assert len(report.fat_files) == 1
    assert "21 functions" in report.fat_files[0]["reason"]


def test_fat_by_both_thresholds():
    """File exceeding both thresholds shows both reasons."""
    file = FileComplexity(
        path="/path/to/huge.py",
        loc=600,
        functions=[
            FunctionComplexity(
                name=f"func_{i}", line=1, complexity=1, func_loc=5, param_count=0
            )
            for i in range(30)
        ],
    )
    report = compute_fatness([file])
    assert len(report.fat_files) == 1
    reasons = report.fat_files[0]["reason"]
    assert "600 LOC" in reasons
    assert "30 functions" in reasons


def test_fat_function_by_loc():
    """Function exceeding 50 LOC is marked as fat."""
    file = FileComplexity(
        path="/path/to/file.py",
        loc=100,
        functions=[
            FunctionComplexity(
                name="big_func",
                line=1,
                complexity=5,
                func_loc=51,
                param_count=1,
            )
        ],
    )
    report = compute_fatness([file])
    assert len(report.fat_functions) == 1
    assert report.fat_functions[0]["name"] == "big_func"
    assert report.fat_functions[0]["func_loc"] == 51
    assert "51 LOC" in report.fat_functions[0]["reason"]


def test_fat_function_by_param_count():
    """Function with 6+ params is marked as fat."""
    file = FileComplexity(
        path="/path/to/file.py",
        loc=100,
        functions=[
            FunctionComplexity(
                name="many_params",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=6,
            )
        ],
    )
    report = compute_fatness([file])
    assert len(report.fat_functions) == 1
    assert report.fat_functions[0]["name"] == "many_params"
    assert report.fat_functions[0]["param_count"] == 6
    assert "6 params" in report.fat_functions[0]["reason"]


def test_fat_function_by_call_count_without_call_graph():
    """No call graph: call_count is 0, threshold not exceeded."""
    file = FileComplexity(
        path="/path/to/file.py",
        loc=100,
        functions=[
            FunctionComplexity(
                name="caller",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=1,
            )
        ],
    )
    report = compute_fatness([file], call_graph=None)
    assert len(report.fat_functions) == 0


def test_fat_function_by_call_count_with_call_graph():
    """Function making 8+ calls is marked as fat."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="caller",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=1,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("caller", file.path): FunctionDefinition(
                name="caller",
                filepath=file.path,
                line=1,
                def_type="function",
            )
        },
        calls=[
            CallEdge(
                caller_file=file.path,
                caller_function="caller",
                caller_line=10,
                callee_file=file.path,
                callee_function=f"func_{i}",
                callee_line=20,
                call_site_line=i,
                call_type="local",
            )
            for i in range(8)
        ],
    )
    report = compute_fatness([file], call_graph=call_graph)
    assert len(report.fat_functions) == 1
    assert report.fat_functions[0]["call_count"] == 8
    assert "8 function calls" in report.fat_functions[0]["reason"]


def test_fat_function_by_all_three_thresholds():
    """Function violating all three fatness thresholds."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="huge_func",
                line=1,
                complexity=5,
                func_loc=60,
                param_count=7,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("huge_func", file.path): FunctionDefinition(
                name="huge_func",
                filepath=file.path,
                line=1,
                def_type="function",
            )
        },
        calls=[
            CallEdge(
                caller_file=file.path,
                caller_function="huge_func",
                caller_line=10,
                callee_file=file.path,
                callee_function=f"func_{i}",
                callee_line=20,
                call_site_line=i,
                call_type="local",
            )
            for i in range(10)
        ],
    )
    report = compute_fatness([file], call_graph=call_graph)
    assert len(report.fat_functions) == 1
    reasons = report.fat_functions[0]["reason"]
    assert "60 LOC" in reasons
    assert "7 params" in reasons
    assert "10 function calls" in reasons


def test_multiple_files_multiple_functions():
    """Multiple files and functions with mixed fatness."""
    file1 = FileComplexity(
        path=os.path.abspath("/path/file1.py"),
        loc=400,
        functions=[
            FunctionComplexity(
                name="small",
                line=1,
                complexity=1,
                func_loc=10,
                param_count=1,
            ),
            FunctionComplexity(
                name="big",
                line=20,
                complexity=3,
                func_loc=55,
                param_count=1,
            ),
        ],
    )

    file2 = FileComplexity(
        path=os.path.abspath("/path/file2.py"),
        loc=600,
        functions=[
            FunctionComplexity(
                name="param_heavy",
                line=1,
                complexity=1,
                func_loc=10,
                param_count=8,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("big", file1.path): FunctionDefinition(
                name="big",
                filepath=file1.path,
                line=20,
                def_type="function",
            ),
            ("param_heavy", file2.path): FunctionDefinition(
                name="param_heavy",
                filepath=file2.path,
                line=1,
                def_type="function",
            ),
        },
        calls=[
            CallEdge(
                caller_file=file1.path,
                caller_function="big",
                caller_line=20,
                callee_file=file1.path,
                callee_function="func",
                callee_line=30,
                call_site_line=21,
                call_type="local",
            )
            for _ in range(8)
        ],
    )

    report = compute_fatness([file1, file2], call_graph=call_graph)

    assert len(report.fat_files) == 1
    assert report.fat_files[0]["path"] == os.path.abspath("/path/file2.py")

    assert len(report.fat_functions) == 2
    func_names = {f["name"] for f in report.fat_functions}
    assert "big" in func_names
    assert "param_heavy" in func_names


def test_external_calls_not_counted():
    """External calls (call_type='external') are not counted."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="caller",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=1,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("caller", file.path): FunctionDefinition(
                name="caller",
                filepath=file.path,
                line=1,
                def_type="function",
            )
        },
        calls=[
            CallEdge(
                caller_file=file.path,
                caller_function="caller",
                caller_line=10,
                callee_file=None,
                callee_function="print",
                callee_line=None,
                call_site_line=i,
                call_type="external",
            )
            for i in range(10)
        ],
    )
    report = compute_fatness([file], call_graph=call_graph)
    assert len(report.fat_functions) == 0


def test_call_graph_none_returns_empty_counts():
    """None call_graph returns empty dict, no function calls counted."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="func",
                line=1,
                complexity=1,
                func_loc=10,
                param_count=1,
            )
        ],
    )
    report = compute_fatness([file], call_graph=None)
    assert len(report.fat_functions) == 0


def test_duplicate_calls_counted_as_unique():
    """Duplicate calls to the same function are counted once per unique function."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="caller",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=1,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("caller", file.path): FunctionDefinition(
                name="caller",
                filepath=file.path,
                line=1,
                def_type="function",
            ),
            ("target", file.path): FunctionDefinition(
                name="target",
                filepath=file.path,
                line=20,
                def_type="function",
            ),
        },
        calls=[
            CallEdge(
                caller_file=file.path,
                caller_function="caller",
                caller_line=10,
                callee_file=file.path,
                callee_function="target",
                callee_line=20,
                call_site_line=i,
                call_type="local",
            )
            for i in range(10)
        ],
    )
    report = compute_fatness([file], call_graph=call_graph)
    assert len(report.fat_functions) == 0
    assert report.fat_functions == []


def test_unique_calls_count_not_duplicate_calls():
    """Only unique functions called are counted, not duplicate calls."""
    file = FileComplexity(
        path=os.path.abspath("/path/to/file.py"),
        loc=100,
        functions=[
            FunctionComplexity(
                name="caller",
                line=1,
                complexity=3,
                func_loc=10,
                param_count=1,
            )
        ],
    )

    call_graph = CallGraph(
        functions={
            ("caller", file.path): FunctionDefinition(
                name="caller",
                filepath=file.path,
                line=1,
                def_type="function",
            ),
            ("target1", file.path): FunctionDefinition(
                name="target1",
                filepath=file.path,
                line=20,
                def_type="function",
            ),
            ("target2", file.path): FunctionDefinition(
                name="target2",
                filepath=file.path,
                line=30,
                def_type="function",
            ),
            ("target3", file.path): FunctionDefinition(
                name="target3",
                filepath=file.path,
                line=40,
                def_type="function",
            ),
        },
        calls=[
            CallEdge(
                caller_file=file.path,
                caller_function="caller",
                caller_line=10,
                callee_file=file.path,
                callee_function=callee_name,
                callee_line=20,
                call_site_line=i,
                call_type="local",
            )
            for i, callee_name in enumerate(["target1", "target2", "target3"] * 10)
        ],
    )
    report = compute_fatness([file], call_graph=call_graph)
    assert len(report.fat_functions) == 0
