"""Verify percentage vectors are mathematically correct using independent computation."""

from decimal import Decimal, ROUND_HALF_EVEN

import pytest

from finlint.vectors import VectorFailure, load_suite, run_vector


@pytest.fixture(scope="module")
def vectors():
    return load_suite("percentage")


# --- Blackbox: per-vector correctness ---


def test_pct_001_half_even_rounds_up(vectors):
    """18400/128000 = 14.375% — HALF_EVEN rounds to 14.38 (7 is odd)."""
    v = vectors[0]
    # Verify by long division: 18400/128000 = 23/160 = 0.14375
    fraction = Decimal("23") / Decimal("160")
    result = (fraction * 100).quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN)
    assert result == Decimal(v["expected"]["result"])


def test_pct_002_half_even_rounds_down(vectors):
    """39200/128000 = 30.625% — HALF_EVEN rounds to 30.62 (2 is even)."""
    v = vectors[1]
    # Verify by simplification: 39200/128000 = 49/160 = 0.30625
    fraction = Decimal("49") / Decimal("160")
    result = (fraction * 100).quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN)
    assert result == Decimal(v["expected"]["result"])


def test_pct_003_repeating_decimal(vectors):
    """1/3 = 33.33% (repeating decimal truncated)."""
    v = vectors[2]
    result = (Decimal(v["inputs"]["value"]) / Decimal(v["inputs"]["total"]) * 100).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_EVEN
    )
    assert result == Decimal(v["expected"]["result"])


def test_pct_004_identity(vectors):
    """5000/5000 = exactly 100.00%."""
    v = vectors[3]
    result = (Decimal(v["inputs"]["value"]) / Decimal(v["inputs"]["total"]) * 100).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_EVEN
    )
    assert result == Decimal(v["expected"]["result"])


def test_pct_005_tiny_rounds_to_zero(vectors):
    """0.01/10000 = 0.0001% rounds to 0.00%."""
    v = vectors[4]
    result = (Decimal(v["inputs"]["value"]) / Decimal(v["inputs"]["total"]) * 100).quantize(
        Decimal("0.01"), rounding=ROUND_HALF_EVEN
    )
    assert result == Decimal(v["expected"]["result"])
    assert result >= 0  # Must not be negative


def test_pct_006_sum_to_100(vectors):
    """4 holdings' percentages sum to exactly 100.00%."""
    v = vectors[5]
    total = Decimal(v["inputs"]["total"])
    percentages = [
        (Decimal(val) / total * 100).quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN)
        for val in v["inputs"]["values"]
    ]
    expected = [Decimal(s) for s in v["expected"]["percentages"]]
    assert percentages == expected
    assert sum(percentages) == Decimal(v["expected"]["sum"])


# --- Blackbox: invariant ---


def test_percentage_at_most_100(vectors):
    """All single-value percentages are between 0 and 100 inclusive."""
    for v in vectors:
        if "result" in v["expected"]:
            result = Decimal(v["expected"]["result"])
            assert Decimal("0") <= result <= Decimal("100"), (
                f"Vector {v['id']}: {result} not in [0, 100]"
            )


# --- Blackbox: float produces exact wrong answer ---


def test_float_pct_001_gives_14_37():
    """Float: 18400/128000*100 → 14.374999..., round → 14.37 (not 14.38)."""
    float_result = round(18400 / 128000 * 100, 2)
    assert float_result == 14.37, f"Expected float to give 14.37, got {float_result}"


def test_float_pct_002_gives_30_63():
    """Float: 39200/128000*100 → 30.625000..04, round → 30.63 (not 30.62)."""
    float_result = round(39200 / 128000 * 100, 2)
    assert float_result == 30.63, f"Expected float to give 30.63, got {float_result}"


def test_float_pct_006_sums_to_99_99():
    """Float: individual percentages sum to ~99.99, not 100.00."""
    holdings = [3375, 5625, 6375, 4625]
    total = 20000
    float_pcts = [round(h / total * 100, 2) for h in holdings]
    assert abs(sum(float_pcts) - 99.99) < 0.01


# --- Whitebox: error reporting ---


def test_wrong_impl_raises_with_vector_id(vectors):
    """VectorFailure message includes vector ID."""
    v = vectors[0]

    def wrong_impl(**kwargs):
        return {"result": Decimal("0")}

    with pytest.raises(VectorFailure, match=r"pct-001"):
        run_vector(v, wrong_impl)


def test_wrong_impl_raises_with_path(vectors):
    """VectorFailure message includes path to mismatch."""
    v = vectors[0]

    def wrong_impl(**kwargs):
        return {"result": Decimal("0")}

    with pytest.raises(VectorFailure, match=r"\.result"):
        run_vector(v, wrong_impl)


def test_precision_boundary(vectors):
    """Result at exactly the tolerance boundary passes (≤ not <)."""
    v = vectors[0]
    precision = Decimal(v["precision"])
    expected = Decimal(v["expected"]["result"])

    def boundary_impl(**kwargs):
        return {"result": expected + precision}

    run_vector(v, boundary_impl)


def test_missing_key_in_result(vectors):
    """Implementation missing output key raises VectorFailure."""
    v = vectors[0]

    def missing_key_impl(**kwargs):
        return {"wrong_key": Decimal("0")}

    with pytest.raises(VectorFailure, match=r"missing"):
        run_vector(v, missing_key_impl)
