from __future__ import annotations

import pytest
from collections.abc import Callable

from fastapi.testclient import TestClient

from services.unit_of_work import UnitOfWork


def _create_project(client: TestClient, name: str) -> int:
    response = client.post(
        "/projects",
        json={
            "name": name,
            "location": "Nevada",
            "operation_type": "open_pit",
            "description": "Project for capex testing",
        },
    )
    assert response.status_code == 201
    return response.json()["id"]


def _create_scenario(client: TestClient, project_id: int, name: str) -> int:
    response = client.post(
        f"/projects/{project_id}/scenarios",
        json={
            "name": name,
            "description": "Capex scenario",
            "status": "draft",
            "currency": "usd",
            "primary_resource": "diesel",
        },
    )
    assert response.status_code == 201
    return response.json()["id"]


def test_capex_calculation_html_flow(
    client: TestClient,
    unit_of_work_factory: Callable[[], UnitOfWork],
) -> None:
    project_id = _create_project(client, "Capex HTML Project")
    scenario_id = _create_scenario(client, project_id, "Capex HTML Scenario")

    form_page = client.get(
        f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}"
    )
    assert form_page.status_code == 200
    assert "Capex Planner" in form_page.text

    response = client.post(
        f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}",
        data={
            "components[0][name]": "Crusher",
            "components[0][category]": "equipment",
            "components[0][amount]": "1200000",
            "components[0][currency]": "USD",
            "components[0][spend_year]": "0",
            "components[1][name]": "Conveyor",
            "components[1][category]": "equipment",
            "components[1][amount]": "800000",
            "components[1][currency]": "USD",
            "components[1][spend_year]": "1",
            "parameters[currency_code]": "USD",
            "parameters[contingency_pct]": "5",
            "parameters[discount_rate_pct]": "8",
            "parameters[evaluation_horizon_years]": "5",
            "options[persist]": "1",
        },
    )
    assert response.status_code == 200
    assert "Capex calculation completed successfully." in response.text
    assert "Capex Summary" in response.text
    assert "$1,200,000.00" in response.text or "1,200,000" in response.text
    assert "USD" in response.text

    with unit_of_work_factory() as uow:
        assert uow.project_capex is not None
        assert uow.scenario_capex is not None

        project_snapshots = uow.project_capex.list_for_project(project_id)
        scenario_snapshots = uow.scenario_capex.list_for_scenario(scenario_id)

        assert len(project_snapshots) == 1
        assert len(scenario_snapshots) == 1

        project_snapshot = project_snapshots[0]
        scenario_snapshot = scenario_snapshots[0]

        assert project_snapshot.total_capex is not None
        assert project_snapshot.contingency_pct is not None
        assert project_snapshot.total_with_contingency is not None
        assert float(project_snapshot.total_capex) == pytest.approx(2_000_000)
        assert float(project_snapshot.contingency_pct) == pytest.approx(5.0)
        assert float(project_snapshot.total_with_contingency) == pytest.approx(
            2_100_000)
        assert project_snapshot.component_count == 2
        assert project_snapshot.currency_code == "USD"

        assert scenario_snapshot.total_capex is not None
        assert scenario_snapshot.contingency_amount is not None
        assert scenario_snapshot.total_with_contingency is not None
        assert float(scenario_snapshot.total_capex) == pytest.approx(2_000_000)
        assert float(
            scenario_snapshot.contingency_amount) == pytest.approx(100_000)
        assert float(scenario_snapshot.total_with_contingency) == pytest.approx(
            2_100_000)
        assert scenario_snapshot.component_count == 2
        assert scenario_snapshot.currency_code == "USD"
        assert scenario_snapshot.payload is not None


def test_capex_calculation_json_flow(
    client: TestClient,
    unit_of_work_factory: Callable[[], UnitOfWork],
) -> None:
    project_id = _create_project(client, "Capex JSON Project")
    scenario_id = _create_scenario(client, project_id, "Capex JSON Scenario")

    payload = {
        "components": [
            {
                "name": "Camp",
                "category": "infrastructure",
                "amount": 600000,
                "currency": "USD",
                "spend_year": 0,
            },
            {
                "name": "Power",
                "category": "infrastructure",
                "amount": 400000,
                "currency": "USD",
                "spend_year": 1,
            },
        ],
        "parameters": {
            "currency_code": "USD",
            "contingency_pct": 12.5,
            "discount_rate_pct": 6.5,
            "evaluation_horizon_years": 4,
        },
        "options": {"persist": True},
    }

    response = client.post(
        f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}",
        json=payload,
    )
    assert response.status_code == 200

    data = response.json()
    assert data["currency"] == "USD"
    assert data["totals"]["overall"] == 1_000_000
    assert data["totals"]["contingency_pct"] == pytest.approx(12.5)
    assert data["totals"]["contingency_amount"] == pytest.approx(125000)
    assert data["totals"]["with_contingency"] == pytest.approx(1_125_000)

    by_category = {row["category"]: row for row in data["totals"]["by_category"]}
    assert by_category["infrastructure"]["amount"] == pytest.approx(1_000_000)
    assert by_category["infrastructure"]["share"] == pytest.approx(100)

    assert len(data["timeline"]) == 2
    assert data["timeline"][0]["year"] == 0
    assert data["timeline"][0]["spend"] == pytest.approx(600_000)
    assert data["timeline"][1]["cumulative"] == pytest.approx(1_000_000)

    with unit_of_work_factory() as uow:
        assert uow.project_capex is not None
        assert uow.scenario_capex is not None

        scenario_snapshot = uow.scenario_capex.latest_for_scenario(scenario_id)
        project_snapshot = uow.project_capex.latest_for_project(project_id)

        assert scenario_snapshot is not None
        assert project_snapshot is not None

        assert scenario_snapshot.total_capex is not None
        assert project_snapshot.total_with_contingency is not None
        assert float(scenario_snapshot.total_capex) == pytest.approx(1_000_000)
        assert float(project_snapshot.total_with_contingency) == pytest.approx(
            1_125_000)

        assert scenario_snapshot.payload is not None
        payload = scenario_snapshot.payload or {}
        result_payload = payload.get("result", {})
        assert result_payload.get("currency") == "USD"
        assert result_payload.get("totals", {}).get(
            "with_contingency") == pytest.approx(1_125_000)
        assert result_payload.get("totals", {}).get(
            "overall") == pytest.approx(1_000_000)
