"""Tests for Python cognitive complexity."""

from vibelexity.analyzers.python import analyze_source


def _score(code: str) -> int:
    """Helper: return total complexity of the first function in code."""
    result = analyze_source(code)
    assert result.functions, f"No functions found in:\n{code}"
    return result.functions[0].complexity


def _scores(code: str) -> dict[str, int]:
    """Helper: return {name: complexity} for all functions in code."""
    result = analyze_source(code)
    return {f.name: f.complexity for f in result.functions}


# --- Basic control flow ---


def test_simple_function():
    assert _score("def f(): pass") == 0


def test_single_if():
    code = """
def f(x):
    if x:       # +1
        pass
"""
    assert _score(code) == 1


def test_if_else():
    code = """
def f(x):
    if x:       # +1
        pass
    else:       # +1
        pass
"""
    assert _score(code) == 2


def test_if_elif_else():
    code = """
def f(x):
    if x > 0:       # +1
        pass
    elif x < 0:     # +1
        pass
    else:            # +1
        pass
"""
    assert _score(code) == 3


def test_for_loop():
    code = """
def f(items):
    for x in items:   # +1
        pass
"""
    assert _score(code) == 1


def test_while_loop():
    code = """
def f(x):
    while x > 0:   # +1
        x -= 1
"""
    assert _score(code) == 1


# --- Nesting ---


def test_nested_if():
    code = """
def f(a, b):
    if a:           # +1 (nesting 0)
        if b:       # +1 + 1 (nesting 1)
            pass
"""
    assert _score(code) == 3


def test_deeply_nested():
    code = """
def f(a, b, c):
    if a:               # +1 (nesting 0)
        for x in b:     # +1 + 1 (nesting 1)
            if c:        # +1 + 2 (nesting 2)
                pass
"""
    assert _score(code) == 6


def test_if_else_nesting():
    """else increases nesting for its body but gets no nesting bonus."""
    code = """
def f(a, b):
    if a:           # +1 (nesting 0)
        pass
    else:           # +1 (no nesting bonus)
        if b:       # +1 + 1 (nesting 1, inside else body)
            pass
"""
    assert _score(code) == 4


# --- Try/Except ---


def test_try_except():
    """try has no increment; except gets +1."""
    code = """
def f():
    try:
        pass
    except ValueError:   # +1
        pass
"""
    assert _score(code) == 1


def test_try_except_nested():
    code = """
def f(x):
    if x:                   # +1 (nesting 0)
        try:
            pass
        except ValueError:  # +1 + 1 (nesting 1)
            pass
"""
    assert _score(code) == 3


# --- Boolean operators ---


def test_single_and():
    code = """
def f(a, b):
    if a and b:   # +1 (if) + 1 (boolean)
        pass
"""
    assert _score(code) == 2


def test_chained_same_op():
    code = """
def f(a, b, c):
    if a and b and c:   # +1 (if) + 1 (boolean chain, same op)
        pass
"""
    assert _score(code) == 2


def test_mixed_boolean():
    code = """
def f(a, b, c):
    if a and b or c:   # +1 (if) + 2 (and->or switch)
        pass
"""
    assert _score(code) == 3


def test_complex_boolean():
    code = """
def f(a, b, c, d):
    if a or b or c and d:   # +1 (if) + 2 (or sequence, then switch to and)
        pass
"""
    assert _score(code) == 3


# --- Ternary / conditional expression ---


def test_ternary():
    code = """
def f(x):
    return x if x > 0 else -x   # +1 (ternary, nesting 0)
"""
    assert _score(code) == 1


def test_ternary_nested():
    code = """
def f(x):
    if x:                            # +1 (nesting 0)
        return x if x > 0 else -x   # +1 + 1 (ternary, nesting 1)
"""
    assert _score(code) == 3


# --- Recursion ---


def test_recursion():
    code = """
def factorial(n):
    if n <= 1:                  # +1
        return 1
    return n * factorial(n-1)   # +1 (recursion)
"""
    assert _score(code) == 2


def test_no_false_recursion():
    """Calling a different function shouldn't count as recursion."""
    code = """
def f(x):
    return g(x)
"""
    assert _score(code) == 0


# --- Nested functions ---


def test_nested_function_separate_scoring():
    """Nested functions are scored independently; outer doesn't include inner."""
    code = """
def outer(x):
    if x:           # +1 for outer
        pass
    def inner(y):
        if y:       # +1 for inner
            pass
"""
    scores = _scores(code)
    assert scores["outer"] == 1
    assert scores["inner"] == 1


def test_nested_function_nesting_reset():
    """Nested function starts at nesting=0 regardless of outer nesting."""
    code = """
def outer(x):
    if x:                   # +1 for outer
        def inner(y):
            if y:           # +1 for inner (nesting 0, not 1)
                pass
"""
    scores = _scores(code)
    assert scores["outer"] == 1
    assert scores["inner"] == 1


# --- Lambda ---


def test_lambda_nesting():
    """Lambda increases nesting level."""
    code = """
def f(items):
    return list(map(lambda x: x if x > 0 else 0, items))
"""
    # lambda increases nesting to 1
    # ternary inside lambda: +1 + 1 (nesting 1) = 2
    assert _score(code) == 2


# --- Comprehensive example ---


def test_comprehensive():
    code = """
def process(items, flag):
    if flag:                        # +1 (nesting 0)
        for item in items:         # +1 +1 (nesting 1)
            if item > 0:           # +1 +2 (nesting 2)
                pass
            elif item < 0:         # +1 (no nesting bonus)
                pass
            else:                  # +1 (no nesting bonus)
                try:
                    pass
                except Exception:  # +1 +3 (nesting 3)
                    pass
    else:                           # +1 (no nesting bonus)
        pass
"""
    # 1 + 2 + 3 + 1 + 1 + 4 + 1 = 13
    assert _score(code) == 13


# --- FileComplexity ---


def test_file_total():
    code = """
def f(x):
    if x:   # +1
        pass

def g(a, b):
    if a:       # +1
        if b:   # +1 +1 = 2
            pass
"""
    result = analyze_source(code)
    assert result.total == 4
    assert len(result.functions) == 2


def test_function_lines():
    code = """
def first():
    pass

def second():
    pass
"""
    result = analyze_source(code)
    assert result.functions[0].line == 2
    assert result.functions[0].name == "first"
    assert result.functions[1].line == 5
    assert result.functions[1].name == "second"


# --- Edge cases ---


def test_empty_function():
    assert _score("def f(): pass") == 0


def test_not_operator_no_increment():
    """'not' is a unary operator, not a boolean sequence."""
    code = """
def f(x):
    if not x:   # +1 (if only)
        pass
"""
    assert _score(code) == 1
