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

from vibelexity.analyzers.go 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():
    code = "package main\nfunc f() {}"
    assert _npath(code) == 1


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


# --- If statements ---


def test_if_only():
    code = """
package main
func f(x bool) {
    if x {
        return
    }
}
"""
    assert _npath(code) == 2


def test_if_else():
    code = """
package main
func f(x bool) {
    if x {
        return
    } else {
        return
    }
}
"""
    assert _npath(code) == 2


def test_if_elif_else():
    code = """
package main
func f(x int) {
    if x > 0 {
        return
    } else if x < 0 {
        return
    } else {
        return
    }
}
"""
    assert _npath(code) == 3


def test_if_elif_no_else():
    code = """
package main
func f(x int) {
    if x > 0 {
        return
    } else if x < 0 {
        return
    }
}
"""
    assert _npath(code) == 3  # +1 for no terminal else


# --- Bool ops ---


def test_bool_ops_in_if():
    code = """
package main
func f(a, b bool) {
    if a && b {
        return
    }
}
"""
    assert _npath(code) == 3


def test_two_bool_ops():
    code = """
package main
func f(a, b, c bool) {
    if a && b || c {
        return
    }
}
"""
    assert _npath(code) == 4


# --- Loops ---


def test_for_loop():
    code = """
package main
func f() {
    for i := 0; i < 10; i++ {
        _ = i
    }
}
"""
    assert _npath(code) == 2


def test_for_range():
    code = """
package main
func f(items []int) {
    for _, v := range items {
        _ = v
    }
}
"""
    assert _npath(code) == 2


def test_infinite_for():
    code = """
package main
func f() {
    for {
        break
    }
}
"""
    assert _npath(code) == 2


# --- Switch ---


def test_switch_three_cases():
    code = """
package main
func f(x int) {
    switch x {
    case 1:
        return
    case 2:
        return
    case 3:
        return
    }
}
"""
    assert _npath(code) == 4  # 1+1+1 + 1 (no default)


def test_switch_with_default():
    code = """
package main
func f(x int) {
    switch x {
    case 1:
        return
    case 2:
        return
    default:
        return
    }
}
"""
    assert _npath(code) == 3


def test_type_switch():
    code = """
package main
func f(x interface{}) {
    switch x.(type) {
    case int:
        return
    case string:
        return
    }
}
"""
    assert _npath(code) == 3  # 1+1 + 1 (no default)


# --- Select ---


def test_select():
    code = """
package main
func f(ch1, ch2 chan int) {
    select {
    case v := <-ch1:
        _ = v
    case v := <-ch2:
        _ = v
    }
}
"""
    assert _npath(code) == 3  # 1+1 + 1 (no default)


def test_select_with_default():
    code = """
package main
func f(ch chan int) {
    select {
    case v := <-ch:
        _ = v
    default:
        return
    }
}
"""
    assert _npath(code) == 2


# --- Sequential ifs ---


def test_sequential_ifs():
    code = """
package main
func f(a, b bool) {
    if a {
        return
    }
    if b {
        return
    }
}
"""
    assert _npath(code) == 4


# --- Nested if ---


def test_nested_if():
    code = """
package main
func f(a, b bool) {
    if a {
        if b {
            return
        }
    }
}
"""
    assert _npath(code) == 3


# --- Nested named func_literal ---


def test_nested_named_func_literal():
    code = """
package main
func outer(x bool) {
    if x {
        return
    }
    inner := func(y bool) {
        if y {
            return
        }
    }
    _ = inner
}
"""
    npaths = _npaths(code)
    assert npaths["outer"] == 2  # inner counts as 1
    assert npaths["inner"] == 2


# --- Goroutine with inline closure ---


def test_goroutine_inline_closure():
    code = """
package main
func f(x bool) {
    go func() {
        if x {
            return
        }
    }()
}
"""
    assert _npath(code) == 2


# --- Method declaration ---


def test_method_declaration():
    code = """
package main
type Foo struct{}
func (f Foo) bar(x bool) {
    if x {
        return
    }
}
"""
    assert _npath(code) == 2
