"""Tests for shared Halstead computation logic."""

import math

from vibelexity.common import (
    compute_halstead,
    compute_ldi,
    compute_maintainability_index,
)
from vibelexity.models import HalsteadMetrics


def test_known_input():
    """Verify computed metrics against hand-calculated values."""
    # 3 distinct operators, 4 distinct operands
    # 5 total operators, 7 total operands
    operators = ["+", "+", "*", "*", "="]
    operands = ["x", "y", "z", "x", "y", "z", "w"]

    h = compute_halstead(operators, operands)

    assert h.n1 == 3  # distinct operators: +, *, =
    assert h.n2 == 4  # distinct operands: x, y, z, w
    assert h.N1 == 5  # total operators
    assert h.N2 == 7  # total operands
    assert h.vocabulary == 7  # n = 3 + 4
    assert h.length == 12  # N = 5 + 7

    expected_volume = 12 * math.log2(7)
    assert h.volume == round(expected_volume, 2)

    expected_difficulty = (3 / 2) * (7 / 4)
    assert h.difficulty == round(expected_difficulty, 2)

    expected_effort = expected_difficulty * expected_volume
    assert h.effort == round(expected_effort, 2)

    expected_time = expected_effort / 18
    assert h.time == round(expected_time, 2)

    expected_bugs = expected_effort ** (2 / 3) / 3000
    assert h.bugs == round(expected_bugs, 4)


def test_empty_inputs():
    """Empty operator/operand lists produce zeroed metrics."""
    h = compute_halstead([], [])
    assert h.n1 == 0
    assert h.n2 == 0
    assert h.N1 == 0
    assert h.N2 == 0
    assert h.vocabulary == 0
    assert h.length == 0
    assert h.volume == 0.0
    assert h.difficulty == 0.0
    assert h.effort == 0.0
    assert h.time == 0.0
    assert h.bugs == 0.0


def test_zero_operands_guard():
    """Operators only (no operands) should not crash -- guards n2=0 division."""
    h = compute_halstead(["+", "-"], [])
    assert h.n1 == 2
    assert h.n2 == 0
    assert h.N1 == 2
    assert h.N2 == 0
    assert h.vocabulary == 2
    assert h.length == 2
    # Derived measures should be zero since n2=0
    assert h.volume == 0.0
    assert h.difficulty == 0.0
    assert h.effort == 0.0


def test_zero_operators_guard():
    """Operands only (no operators) should not crash."""
    h = compute_halstead([], ["x", "y"])
    assert h.n1 == 0
    assert h.n2 == 2
    assert h.N1 == 0
    assert h.N2 == 2
    assert h.vocabulary == 2
    assert h.length == 2
    # n=2 and n2=2 so derived measures can be computed, but n1=0 means D=0
    assert h.volume == round(2 * math.log2(2), 2)
    assert h.difficulty == 0.0  # (0/2) * (2/2) = 0


def test_effort_equals_d_times_v():
    """E = D * V relationship holds."""
    operators = ["=", "+", "*"]
    operands = ["a", "b", "c", "a"]
    h = compute_halstead(operators, operands)
    # Verify E = D * V within rounding tolerance
    assert abs(h.effort - h.difficulty * h.volume) < 0.1


def test_time_equals_effort_over_18():
    """T = E / 18 relationship holds."""
    operators = ["=", "+", "-", "="]
    operands = ["x", "y", "z", "result"]
    h = compute_halstead(operators, operands)
    assert abs(h.time - h.effort / 18) < 0.1


def test_dataclass_defaults():
    """HalsteadMetrics defaults are all zero."""
    h = HalsteadMetrics()
    assert h.n1 == 0
    assert h.volume == 0.0
    assert h.bugs == 0.0


def test_single_operator_single_operand():
    """Minimal non-empty program."""
    h = compute_halstead(["="], ["x"])
    assert h.n1 == 1
    assert h.n2 == 1
    assert h.vocabulary == 2
    assert h.length == 2
    assert h.volume == round(2 * math.log2(2), 2)
    assert h.difficulty == round(0.5 * 1.0, 2)


def test_ldi_basic():
    """Verify LDI computation with known values."""
    # Test case: vocabulary=10, n2=5, volume=8, difficulty=2.5
    # Expected: (10/100 * 40) + (2.5/60 * 40) + (5/8 * 200) = 4 + 1.67 + 125 = 130.67, capped at 100
    ldi = compute_ldi(volume=8, vocabulary=10, n2=5, difficulty=2.5)
    assert ldi == 100.0


def test_ldi_within_range():
    """Verify LDI stays within 0-100 range."""
    # Lower vocabulary/difficulty should produce smaller LDI
    h = compute_halstead(["=", "+", "*"], ["a", "b", "c", "a"])
    ldi = compute_ldi(h.volume, h.vocabulary, h.n2, h.difficulty)
    assert 0 <= ldi <= 100


def test_ldi_zero_volume():
    """Zero volume returns 0 LDI."""
    ldi = compute_ldi(volume=0, vocabulary=10, n2=5, difficulty=2.5)
    assert ldi == 0.0


def test_ldi_formula_components():
    """Verify individual components of LDI formula."""
    ldi = compute_ldi(volume=50, vocabulary=20, n2=10, difficulty=30)
    # (20/100 * 40) = 8, (30/60 * 40) = 20, (10/50 * 200) = 40 = 68
    assert round(ldi, 2) == 68.0


def test_ldi_known_input():
    """Calculate LDI from known Halstead metrics."""
    h = compute_halstead(["=", "+", "*"], ["a", "b", "c", "a"])
    # LDI should be computed without error
    ldi = compute_ldi(h.volume, h.vocabulary, h.n2, h.difficulty)
    assert isinstance(ldi, float)
    assert 0 <= ldi <= 100


def test_mi_zero_volume():
    """Zero volume returns max maintainability index."""
    mi = compute_maintainability_index(halstead_volume=0, cyclomatic=5, loc=100)
    assert mi == 100.0


def test_mi_zero_loc():
    """Zero LOC returns max maintainability index."""
    mi = compute_maintainability_index(halstead_volume=100, cyclomatic=5, loc=0)
    assert mi == 100.0


def test_mi_negative_volume():
    """Negative volume returns max maintainability index."""
    mi = compute_maintainability_index(halstead_volume=-1, cyclomatic=5, loc=100)
    assert mi == 100.0


def test_mi_negative_loc():
    """Negative LOC returns max maintainability index."""
    mi = compute_maintainability_index(halstead_volume=100, cyclomatic=5, loc=-1)
    assert mi == 100.0


def test_mi_calculation():
    """Maintainability index is calculated for valid inputs."""
    mi = compute_maintainability_index(halstead_volume=100, cyclomatic=10, loc=50)
    assert isinstance(mi, float)
    assert 0 <= mi <= 100


def test_ldi_negative_volume():
    """Negative volume returns 0 LDI."""
    ldi = compute_ldi(volume=-1, vocabulary=10, n2=5, difficulty=2.5)
    assert ldi == 0.0
