import pytest

from schemas.calculations import (
    CapexCalculationOptions,
    CapexCalculationRequest,
    CapexComponentInput,
    CapexParameters,
)
from services.calculations import calculate_initial_capex
from services.exceptions import CapexValidationError


def _component(**kwargs) -> CapexComponentInput:
    defaults = {
        "id": None,
        "name": "Component",
        "category": "equipment",
        "amount": 1_000_000.0,
        "currency": "USD",
        "spend_year": 0,
        "notes": None,
    }
    defaults.update(kwargs)
    return CapexComponentInput(**defaults)


def test_calculate_initial_capex_success():
    request = CapexCalculationRequest(
        components=[
            _component(name="Crusher", category="equipment",
                       amount=1_200_000, spend_year=0),
            _component(name="Conveyor", category="equipment",
                       amount=800_000, spend_year=1),
            _component(name="Camp", category="infrastructure",
                       amount=600_000, spend_year=1, currency="usd"),
        ],
        parameters=CapexParameters(
            currency_code="USD",
            contingency_pct=10,
            discount_rate_pct=8,
            evaluation_horizon_years=5,
        ),
        options=CapexCalculationOptions(persist=True),
    )

    result = calculate_initial_capex(request)

    assert result.currency == "USD"
    assert result.options.persist is True

    assert result.totals.overall == pytest.approx(2_600_000)
    assert result.totals.contingency_pct == pytest.approx(10)
    assert result.totals.contingency_amount == pytest.approx(260_000)
    assert result.totals.with_contingency == pytest.approx(2_860_000)

    by_category = {row.category: row for row in result.totals.by_category}
    assert by_category["equipment"].amount == pytest.approx(2_000_000)
    assert by_category["infrastructure"].amount == pytest.approx(600_000)
    assert by_category["equipment"].share == pytest.approx(76.923, rel=1e-3)
    assert by_category["infrastructure"].share == pytest.approx(
        23.077, rel=1e-3)

    timeline = {(entry.year, entry.spend): entry.cumulative for entry in result.timeline}
    assert timeline[(0, 1_200_000)] == pytest.approx(1_200_000)
    assert timeline[(1, 1_400_000)] == pytest.approx(2_600_000)

    assert len(result.components) == 3
    assert result.components[2].currency == "USD"


def test_calculate_initial_capex_currency_mismatch_raises():
    request = CapexCalculationRequest(
        components=[
            _component(amount=500_000, currency="USD"),
        ],
        parameters=CapexParameters(currency_code="CAD"),
    )

    with pytest.raises(CapexValidationError) as exc:
        calculate_initial_capex(request)

    assert "Component currency does not match" in exc.value.message
    assert exc.value.field_errors and "components[0].currency" in exc.value.field_errors[0]


def test_calculate_initial_capex_requires_components():
    request = CapexCalculationRequest(components=[])

    with pytest.raises(CapexValidationError) as exc:
        calculate_initial_capex(request)

    assert "At least one capex component" in exc.value.message
    assert exc.value.field_errors and "components" in exc.value.field_errors[0]
