"""Tests for Python indirection analysis."""

from vibelexity.analyzers.python import analyze_source
from vibelexity.metrics.indirection.python import (
    check_factory_functions,
    check_high_indirection_depth,
)


def _violations(code: str, patterns: list[str] | None = None) -> dict:
    """Helper: return {type: list of violations} for code."""
    from vibelexity.parsers.python import parse

    root = parse(code)
    violations = []

    if patterns is None or "factory" in patterns:
        violations.extend(check_factory_functions(root))
    if patterns is None or "high_indirection_depth" in patterns:
        violations.extend(check_high_indirection_depth(root))

    result = {}
    for v in violations:
        if v.type not in result:
            result[v.type] = []
        result[v.type].append(v)
    return result


# --- Factory function detection ---


def test_no_factory_functions():
    code = """
def foo(x):
    return x + 1

def bar(x):
    return x * 2
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 0


def test_simple_factory_function():
    code = """
def create_analyzer():
    def analyze(code):
        return code
    return analyze
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 1
    assert violations["factory_function"][0].line == 2


def test_factory_with_create_in_name():
    code = """
def create_parser():
    return lambda x: x
"""
    violations = _violations(code, patterns=["factory"])
    # Note: this requires a lambda return, not a nested function
    # This test verifies the pattern detection logic
    # Since create_parser has 'create' in name and returns lambda, it should be detected
    # But our current logic checks for nested functions too
    # Let's adjust for the lambda-only case
    assert len(violations.get("factory_function", [])) == 1


def test_function_returns_named_function():
    code = """
def get_validator():
    return validate

def validate(x):
    return x > 0
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 0


# --- High indirection depth detection ---


def test_no_high_indirection():
    code = """
def a():
    return x()

def x():
    return 1
"""
    violations = _violations(code, patterns=["high_indirection_depth"])
    assert len(violations.get("high_indirection_depth", [])) == 0


def test_high_indirection_depth():
    code = """
def a():
    return b()

def b():
    return c()

def c():
    return d()

def d():
    return 1
"""
    violations = _violations(code, patterns=["high_indirection_depth"])
    assert len(violations.get("high_indirection_depth", [])) == 1
    assert violations["high_indirection_depth"][0].line == 2


def test_multiple_high_indirection():
    code = """
def a():
    return b()

def b():
    return c()

def c():
    return d()

def d():
    return e()

def e():
    return f()

def f():
    return 1
"""
    violations = _violations(code, patterns=["high_indirection_depth"])
    assert len(violations.get("high_indirection_depth", [])) >= 2


# --- Combined pattern detection ---


def test_combined_patterns():
    code = """
def create_wrapper():
    def inner(x):
        return outer(x)
    return inner

def outer(x):
    return inner(x)

def inner(x):
    return 1
"""
    from vibelexity.parsers.python import parse

    root = parse(code)
    violations = check_factory_functions(root) + check_high_indirection_depth(root)
    types = {v.type for v in violations}
    assert "factory_function" in types or "high_indirection_depth" in types


def test_empty_file():
    code = ""
    violations = _violations(code)
    assert len(violations) == 0


def test_function_without_calls():
    code = """
def foo():
    pass

def bar():
    x = 1
"""
    violations = _violations(code, patterns=["high_indirection_depth"])
    assert len(violations.get("high_indirection_depth", [])) == 0


def test_nested_functions_not_counted():
    code = """
def outer():
    def inner():
        return func()
    return inner

def func():
    return 1
"""
    violations = _violations(code, patterns=["high_indirection_depth"])
    # Nested function chains shouldn't trigger high indirection warnings
    # since they're within the same outer scope
    assert len(violations.get("high_indirection_depth", [])) == 0


# --- Factory pattern variations ---


def test_lambda_factory():
    code = """
def create_multiplier(n):
    return lambda x: x * n
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 1


def test_decorator_factory():
    code = """
def create_decorator(name):
    def wrapper(func):
        func.name = name
        return func
    return wrapper
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 1


def test_make_prefix_factory():
    code = """
def make_validator():
    def validate(x):
        return x > 0
    return validate
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 1


# --- Module-level patterns ---


def test_module_level_factory():
    code = """
create_parser = lambda: Parser()
"""
    violations = _violations(code, patterns=["factory"])
    assert len(violations.get("factory_function", [])) == 0


# --- Integration with analyzer ---


def test_indirection_violations_in_result():
    code = """
def a():
    return b()

def b():
    return c()

def c():
    return d()

def d():
    return 1
"""
    result = analyze_source(code)
    indirection_violations = [
        v
        for v in result.pattern_violations
        if v.type in ("high_indirection_depth", "factory_function")
    ]
    assert len(indirection_violations) > 0
