from __future__ import annotations

from decimal import Decimal

import pytest
from fastapi import HTTPException

from jarvis_finance.services.budget_accounts import confirm_create_budget_account
from jarvis_finance.services.budget_categories import confirm_create_category
from jarvis_finance.services.budget_imports import seed_credit_card_candidates_from_rows
from jarvis_finance.services.budget_planning import (
    confirm_category_budget_values,
    get_budget_planning_matrix,
    preview_category_budget_values,
)
from jarvis_finance.services.budget_transactions import confirm_budget_transaction
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


def db():
    conn = connect_memory()
    apply_migrations(conn)
    return conn


def setup_category(conn):
    account_id = confirm_create_budget_account(conn, {"name": "Haushalt", "account_type": "checking", "currency": "CHF"})["entity_id"]
    category_id = confirm_create_category(conn, {"name": "Elektronische Medien", "category_type": "expense"})["entity_id"]
    return account_id, category_id


def book_expense(conn, account_id: str, category_id: str, date: str, amount: str, tx_type: str = "expense"):
    return confirm_budget_transaction(conn, {
        "account_id": account_id,
        "transaction_type": tx_type,
        "transaction_date": date,
        "description": "confirmed booking",
        "payee": "Provider",
        "amount_original": amount,
        "currency_original": "CHF",
        "category_id": category_id,
        "source_type": "manual",
    })


def test_preview_and_confirm_budget_values_store_decimal_text_baselines_and_audit() -> None:
    conn = db()
    _account_id, category_id = setup_category(conn)

    preview = preview_category_budget_values(conn, category_id, {
        "year": "2026",
        "monthly_budget_chf": "120.00",
        "budget_cadence": "monthly",
        "previous_year_total_chf": "600.00",
        "distribute_previous_year_total": True,
        "notes": "planning v1",
    })
    assert preview["review"]["budget_month_chf"] == "120.00"
    assert preview["review"]["budget_year_chf"] == "1440.00"
    assert preview["review"]["previous_year_total_chf"] == "600.00"
    assert preview["review"]["previous_year_monthly_average_chf"] == "50.00"
    assert preview["warnings"] == []

    result = confirm_category_budget_values(conn, category_id, preview["payload"])

    assert result["status"] == "confirmed"
    plan = conn.execute("SELECT monthly_amount_chf, annual_amount_chf, cadence FROM budget_plan_items WHERE category_id=?", (category_id,)).fetchone()
    assert plan["monthly_amount_chf"] == "120.00"
    assert plan["annual_amount_chf"] == "1440.00"
    assert plan["cadence"] == "monthly"
    baselines = conn.execute("SELECT amount_text, baseline_type FROM budget_category_baselines WHERE category_id=? AND year='2025' ORDER BY month", (category_id,)).fetchall()
    assert len(baselines) == 12
    assert {row["amount_text"] for row in baselines} == {"50.00"}
    assert {row["baseline_type"] for row in baselines} == {"actual_previous_year"}
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action IN ('budget_planning_preview','budget_planning_confirmed')").fetchone()[0] >= 2


def test_yearly_budget_derives_monthly_and_monthly_previous_year_sums_to_year() -> None:
    conn = db()
    _account_id, category_id = setup_category(conn)
    monthly_2025 = {f"{idx:02d}": str(idx) for idx in range(1, 13)}

    preview = preview_category_budget_values(conn, category_id, {
        "year": "2026",
        "annual_budget_chf": "1200.00",
        "budget_cadence": "annual",
        "previous_year_months": monthly_2025,
    })

    assert preview["review"]["budget_month_chf"] == "100.00"
    assert preview["review"]["budget_year_chf"] == "1200.00"
    assert preview["review"]["previous_year_total_chf"] == "78.00"
    assert preview["review"]["previous_year_monthly_average_chf"] == "6.50"


def test_negative_budget_values_and_non_expense_categories_are_blocked() -> None:
    conn = db()
    income_category = confirm_create_category(conn, {"name": "Lohn", "category_type": "income"})["entity_id"]
    with pytest.raises(HTTPException):
        preview_category_budget_values(conn, income_category, {"year": "2026", "monthly_budget_chf": "100.00"})
    _account_id, expense_category = setup_category(conn)
    with pytest.raises(HTTPException):
        preview_category_budget_values(conn, expense_category, {"year": "2026", "monthly_budget_chf": "-1.00"})


def test_forecast_matrix_uses_confirmed_expenses_only_and_excludes_candidates_and_transfers() -> None:
    conn = db()
    account_id, category_id = setup_category(conn)
    confirm_category_budget_values(conn, category_id, preview_category_budget_values(conn, category_id, {
        "year": "2026",
        "monthly_budget_chf": "100.00",
        "previous_year_months": {"01": "80.00", "02": "120.00"},
    })["payload"])
    book_expense(conn, account_id, category_id, "2026-01-10", "50.00")
    book_expense(conn, account_id, category_id, "2026-02-10", "150.00")
    book_expense(conn, account_id, category_id, "2026-02-11", "999.00", tx_type="transfer")
    seed_credit_card_candidates_from_rows(conn, [{"Datum": "2026-02-12", "Beschreibung": "candidate only", "Betrag": "999.00"}], source_file_label="visa.csv", migros_covered=False)

    matrix = get_budget_planning_matrix(conn, year="2026", current_month="2026-02")
    row = next(item for item in matrix["rows"] if item["category_id"] == category_id)

    assert row["actual_year_to_date_chf"] == "200.00"
    assert row["actual_months_2026"]["01"] == "50.00"
    assert row["actual_months_2026"]["02"] == "150.00"
    assert row["previous_year_total_chf"] == "200.00"
    assert row["previous_year_monthly_average_chf"] == "16.67"
    assert row["linear_forecast_chf"] == "1200.00"
    assert row["budget_adjusted_forecast_chf"] == "1200.00"
    assert row["forecast_vs_budget_chf"] == "0.00"
    assert row["forecast_vs_previous_year_chf"] == "1000.00"
    assert row["status"] == "Gelb"
    assert row["data_explorer_url"].endswith(f"category_id={category_id}")
    assert row["effective_expenses_url"].endswith(f"category_id={category_id}")


def test_categories_without_budget_or_previous_year_are_explicitly_visible() -> None:
    conn = db()
    account_id, category_id = setup_category(conn)
    book_expense(conn, account_id, category_id, "2026-03-01", "42.00")

    matrix = get_budget_planning_matrix(conn, year="2026", current_month="2026-03")
    row = next(item for item in matrix["rows"] if item["category_id"] == category_id)

    assert row["has_budget"] is False
    assert row["has_previous_year"] is False
    assert row["status"] == "Budget fehlt"
    assert category_id in matrix["categories_without_budget"]
    assert category_id in matrix["categories_without_previous_year"]
