from __future__ import annotations

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, list_category_tree_grouped, simplify_budget_categories_2026
from jarvis_finance.services.budget_imports import (
    archive_reference_year_candidates,
    batch_mark_candidates,
    list_transaction_candidates,
    preview_batch_confirm_candidates,
    seed_transaction_candidates_from_rows,
)
from jarvis_finance.services.budget_overview import get_budget_charts_2026, get_budget_status_by_category
from jarvis_finance.services.budget_transactions import confirm_budget_transaction, list_budget_transactions
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 base(conn):
    account = confirm_create_budget_account(conn, {"name": "Budget 2026 Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense", "sort_order": 10})["entity_id"]
    health = confirm_create_category(conn, {"name": "Gesundheit / Medizin", "category_type": "expense", "sort_order": 40})["entity_id"]
    salary = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income", "sort_order": 10})["entity_id"]
    return account, food, health, salary


def test_phase110_review_and_transactions_default_to_budget_year_2026_and_block_2025_confirm() -> None:
    conn = db(); account, food, _health, _salary = base(conn)
    seed_transaction_candidates_from_rows(conn, [
        {"Datum": "2025-12-30", "Beschreibung": "Alt Referenz", "Betrag": "10.00"},
        {"Datum": "2026-01-03", "Beschreibung": "Aktuell", "Betrag": "11.00"},
    ], source_file_label="phase110.csv")
    rows = list_transaction_candidates(conn, budget_year="2026")
    assert [r["description"] for r in rows] == ["Aktuell"]
    ref = list_transaction_candidates(conn, budget_year="2025", include_reference=True)
    assert [r["description"] for r in ref] == ["Alt Referenz"]
    with pytest.raises(HTTPException):
        preview_batch_confirm_candidates(conn, candidate_ids=[ref[0]["transaction_candidate_id"]], account_id=account, budget_year="2026")
    cleanup = archive_reference_year_candidates(conn, active_year="2026", reference_year="2025")
    assert cleanup["updated_count"] == 1
    archived = conn.execute("SELECT status FROM budget_transaction_candidates WHERE description='Alt Referenz'").fetchone()["status"]
    assert archived == "reference_2025"

    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2025-12-31", "description": "Old tx", "amount_original": "1.00", "currency_original": "CHF", "category_id": food})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-01-31", "description": "New tx", "amount_original": "2.00", "currency_original": "CHF", "category_id": food})
    assert [t["description"] for t in list_budget_transactions(conn, transaction_type="expense", status="confirmed", year="2026")] == ["New tx"]


def test_phase110_bulk_assignment_confirm_ignore_reports_per_candidate_errors() -> None:
    conn = db(); account, food, health, _salary = base(conn)
    seed_transaction_candidates_from_rows(conn, [
        {"Datum": "2026-01-01", "Beschreibung": "A", "Betrag": "1.00"},
        {"Datum": "2026-01-02", "Beschreibung": "B", "Betrag": "2.00"},
        {"Datum": "2025-01-02", "Beschreibung": "Old", "Betrag": "3.00"},
    ], source_file_label="phase110.csv")
    ids = [r["transaction_candidate_id"] for r in list_transaction_candidates(conn, budget_year="2026")]
    result = batch_mark_candidates(conn, candidate_ids=ids, action="category", category_id=food, budget_year="2026")
    assert result["updated_count"] == 2
    assert result["errors"] == []
    preview = preview_batch_confirm_candidates(conn, candidate_ids=ids, account_id=account, budget_year="2026")
    confirmed = batch_mark_candidates(conn, candidate_ids=ids, action="confirm", account_id=account, preview_id=preview["preview_id"], budget_year="2026")
    assert confirmed["confirmed_count"] == 2
    assert len(list_budget_transactions(conn, transaction_type="expense", status="confirmed", year="2026")) == 2

    old_id = conn.execute("SELECT transaction_candidate_id FROM budget_transaction_candidates WHERE transaction_date LIKE '2025-%'").fetchone()[0]
    bad = batch_mark_candidates(conn, candidate_ids=[old_id], action="category", category_id=health, budget_year="2026")
    assert bad["updated_count"] == 0
    assert bad["errors"] and bad["errors"][0]["candidate_id"] == old_id


def test_phase110_category_tree_grouped_and_cleanup_canonicalises_duplicates_and_uat() -> None:
    conn = db(); _account, food, _health, salary = base(conn)
    duplicate = confirm_create_category(conn, {"name": "Sonstiges", "category_type": "expense"})["entity_id"]
    confirm_create_category(conn, {"name": "Sonstiges / Administration", "category_type": "expense"})
    confirm_create_category(conn, {"name": "UAT12 Dummy", "category_type": "expense"})
    confirm_create_category(conn, {"name": "Lohn Melanie", "category_type": "income"})
    result = simplify_budget_categories_2026(conn)
    assert result["ensured_income_count"] >= 5
    assert result["ensured_expense_count"] >= 13
    assert result["archived_test_count"] >= 1
    tree = list_category_tree_grouped(conn)
    assert [bucket["name"] for bucket in tree] == ["Einnahmen", "Ausgaben"]
    income_names = {c["name"] for c in tree[0]["children"]}
    expense_names = {c["name"] for c in tree[1]["children"]}
    assert {"Lohn Marcel", "Lohn Melanie", "Rückerstattungen"}.issubset(income_names)
    assert {"Essen & Haushalt", "Sonstiges / Administration", "Review nötig"}.issubset(expense_names)
    assert not conn.execute("SELECT 1 FROM budget_categories WHERE category_id=? AND is_active=1", (duplicate,)).fetchone()


def test_phase110_budgetstatus_monthly_matrix_forecast_ampel_and_charts_are_2026_only() -> None:
    conn = db(); account, food, _health, salary = base(conn)
    conn.execute("INSERT INTO budget_plan_items(plan_item_id, category_id, name, plan_month, monthly_amount_chf, annual_amount_chf, cadence, is_fixed_cost, source_type, is_active, created_at, updated_at) VALUES ('p_food', ?, 'Food plan', '2026-01', '100.00', NULL, 'monthly', 0, 'manual', 1, '2026-01-01T00:00:00', '2026-01-01T00:00:00')", (food,))
    conn.execute("INSERT INTO budget_plan_items(plan_item_id, category_id, name, plan_month, monthly_amount_chf, annual_amount_chf, cadence, is_fixed_cost, source_type, is_active, created_at, updated_at) VALUES ('p_salary', ?, 'Salary plan', '2026-01', '1000.00', NULL, 'monthly', 0, 'manual', 1, '2026-01-01T00:00:00', '2026-01-01T00:00:00')", (salary,))
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-01-10", "description": "Food Jan", "amount_original": "50.00", "currency_original": "CHF", "category_id": food})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-02-10", "description": "Food Feb", "amount_original": "75.00", "currency_original": "CHF", "category_id": food})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "income", "transaction_date": "2026-01-25", "description": "Salary Jan", "amount_original": "1000.00", "currency_original": "CHF", "category_id": salary})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2025-01-10", "description": "Old Food", "amount_original": "999.00", "currency_original": "CHF", "category_id": food})
    rows = get_budget_status_by_category(conn, year="2026")
    food_row = next(r for r in rows if r["category_id"] == food)
    assert food_row["monthly_actuals"]["01"] == "50.00"
    assert food_row["monthly_actuals"]["02"] == "75.00"
    assert food_row["actual_year_to_date"] == "125.00"
    assert food_row["monthly_actuals"]["12"] == "0.00"
    assert "999" not in food_row["actual_year_to_date"]
    assert food_row["status"] in {"Grün", "Gelb", "Rot", "Kritisch"}
    charts = get_budget_charts_2026(conn, year="2026")
    assert charts["cashflow_by_month"][0]["month"] == "2026-01"
    assert charts["budget_vs_actual_by_category"]
    assert all(item["year"] == "2026" for item in charts["forecast_overruns"])
