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

from decimal import Decimal

import pytest

from finlint.vectors import VectorFailure, load_suite, run_vector


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


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


def test_aggr_001_sum_balances(vectors):
    """Sum 5 balances: reduce in reverse order (independent verification)."""
    v = vectors[0]
    values = [Decimal(s) for s in v["inputs"]["values"]]
    # Verify by summing in reverse order (different from standard left-to-right)
    result = sum(reversed(values))
    assert result == Decimal(v["expected"]["result"])


def test_aggr_002_net_worth(vectors):
    """Net worth = assets - liabilities (verify sum(assets) - sum(liabilities))."""
    v = vectors[1]
    assets = [Decimal(s) for s in v["inputs"]["assets"]]
    liabilities = [Decimal(s) for s in v["inputs"]["liabilities"]]
    result = sum(assets) - sum(liabilities)
    assert result == Decimal(v["expected"]["result"])


def test_aggr_003_repeat_sum(vectors):
    """30 × $0.10 = $3.00 using Decimal multiplication (not loop addition)."""
    v = vectors[2]
    # Independent: multiply instead of adding 30 times
    result = Decimal(v["inputs"]["value"]) * v["inputs"]["count"]
    assert result == Decimal(v["expected"]["result"])


def test_aggr_004_catastrophic_cancel(vectors):
    """1000000.01 - 999999.99 = 0.02."""
    v = vectors[3]
    result = Decimal(v["inputs"]["a"]) - Decimal(v["inputs"]["b"])
    assert result == Decimal(v["expected"]["result"])


def test_aggr_005_dot_product(vectors):
    """Portfolio value: sum of qty × price across 3 positions."""
    v = vectors[4]
    quantities = [Decimal(s) for s in v["inputs"]["quantities"]]
    prices = [Decimal(s) for s in v["inputs"]["prices"]]
    # Verify algebraically: compute each product independently, sum
    result = sum(q * p for q, p in zip(quantities, prices))
    assert result == Decimal(v["expected"]["result"])


def test_aggr_006_cash_flow(vectors):
    """Income minus expenses across multiple items."""
    v = vectors[5]
    income = [Decimal(s) for s in v["inputs"]["income"]]
    expenses = [Decimal(s) for s in v["inputs"]["expenses"]]
    result = sum(income) - sum(expenses)
    assert result == Decimal(v["expected"]["result"])


# --- Blackbox: invariant ---


def test_sum_invariant_nonnegative_inputs(vectors):
    """For aggr-001 (all positive values), sum must be positive and >= max value."""
    v = vectors[0]
    values = [Decimal(s) for s in v["inputs"]["values"]]
    result = Decimal(v["expected"]["result"])
    assert result > 0
    assert result >= max(values)


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


def test_float_repeat_sum_gives_wrong_answer():
    """Naive float loop: 0.1 added 30 times gives 3.0000000000000013, not 3.00."""
    total = 0.0
    for _ in range(30):
        total += 0.1
    assert total != 3.0, "Float should accumulate error in repeated 0.1 addition"
    assert total == pytest.approx(3.0000000000000013)


def test_float_catastrophic_cancellation():
    """Float subtraction of nearly-equal values gives 0.020000000018626451, not 0.02."""
    result = 1000000.01 - 999999.99
    assert result != 0.02
    assert abs(result - 0.02) > 1e-12


# --- 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"aggr-001"):
        run_vector(v, wrong_impl)


def test_wrong_impl_raises_with_path(vectors):
    """VectorFailure message includes path to mismatched field."""
    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}

    # Should NOT raise — diff equals precision (≤ tolerance)
    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)
