"""Tests for NPath complexity (Python)."""

from vibelexity.analyzers.python import analyze_source


def _npath(code: str) -> int:
    """Return NPath of the first function in code."""
    result = analyze_source(code)
    assert result.functions, f"No functions found in:\n{code}"
    npath_val = result.functions[0].npath
    assert npath_val is not None
    return npath_val


def _npaths(code: str) -> dict[str, int]:
    """Return {name: npath} for all functions in code."""
    result = analyze_source(code)
    return {f.name: f.npath for f in result.functions if f.npath is not None}


# --- Simple ---


def test_simple_function():
    assert _npath("def f(): pass") == 1


def test_empty_source():
    result = analyze_source("")
    assert len(result.functions) == 0


# --- If statements ---


def test_if_only():
    code = """
def f(x):
    if x:
        pass
"""
    assert _npath(code) == 2  # NP(if body)=1 + 1 (no else)


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


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


def test_if_elif_no_else():
    code = """
def f(x):
    if x > 0:
        pass
    elif x < 0:
        pass
"""
    assert _npath(code) == 3  # 1 + 1 + 1 (implicit no-else path)


# --- Bool ops in conditions ---


def test_bool_ops_in_if():
    code = """
def f(a, b):
    if a and b:
        pass
"""
    assert _npath(code) == 3  # 1 + 1 (no else) + 1 (one bool op)


def test_two_bool_ops():
    code = """
def f(a, b, c):
    if a and b or c:
        pass
"""
    assert _npath(code) == 4  # 1 + 1 (no else) + 2 (two bool ops)


# --- Loops ---


def test_for_loop():
    code = """
def f(items):
    for x in items:
        pass
"""
    assert _npath(code) == 2  # NP(body)=1 + 1


def test_while_loop():
    code = """
def f(x):
    while x:
        pass
"""
    assert _npath(code) == 2  # NP(body)=1 + 1


def test_while_with_bool_cond():
    code = """
def f(a, b):
    while a and b:
        pass
"""
    assert _npath(code) == 3  # NP(body)=1 + 1 + 1 (bool op)


def test_for_else():
    code = """
def f(items):
    for x in items:
        pass
    else:
        pass
"""
    assert _npath(code) == 2  # NP(body)=1 + NP(else)=1


# --- Try/except ---


def test_try_except():
    code = """
def f():
    try:
        pass
    except:
        pass
"""
    assert _npath(code) == 2  # NP(try body)=1 + NP(except body)=1


# --- Ternary ---


def test_ternary():
    code = """
def f(c, x, y):
    return x if c else y
"""
    assert _npath(code) == 2  # NP(x)=1 + NP(y)=1


# --- Sequential ifs (multiplicative) ---


def test_sequential_ifs():
    code = """
def f(a, b):
    if a:
        pass
    if b:
        pass
"""
    assert _npath(code) == 4  # 2 * 2


# --- Nested if ---


def test_nested_if():
    code = """
def f(a, b):
    if a:
        if b:
            pass
"""
    # Outer if: NP(inner if) + 1 (no else)
    # Inner if: 1 + 1 (no else) = 2
    # Outer: 2 + 1 = 3
    assert _npath(code) == 3


# --- Match ---


def test_match_three_cases():
    code = """
def f(x):
    match x:
        case 1:
            pass
        case 2:
            pass
        case 3:
            pass
"""
    assert _npath(code) == 4  # 1+1+1 + 1 (no wildcard)


def test_match_with_wildcard():
    code = """
def f(x):
    match x:
        case 1:
            pass
        case _:
            pass
"""
    assert _npath(code) == 2  # 1+1, has wildcard


# --- Nested ternary ---


def test_nested_ternary():
    code = """
def f(c1, c2, a, b, d):
    return a if c1 else (b if c2 else d)
"""
    # Outer: NP(a) + NP(inner) = 1 + (1+1) = 3
    assert _npath(code) == 3


# --- Nested function ---


def test_nested_function():
    code = """
def outer(x):
    if x:
        pass
    def inner(y):
        if y:
            pass
"""
    npaths = _npaths(code)
    assert npaths["outer"] == 2  # inner counts as 1
    assert npaths["inner"] == 2


# --- With statement ---


def test_with_statement():
    code = """
def f():
    with open("x") as fp:
        if True:
            pass
"""
    assert _npath(code) == 2  # with body contains if -> npath 2


# --- Lambda with ternary ---


def test_lambda_ternary():
    code = """
def f():
    g = lambda x: x if x > 0 else -x
"""
    # The expression_statement contains a ternary inside the lambda
    # lambda body is recursed: ternary -> 2
    assert _npath(code) == 2
