from __future__ import annotations

from jarvis_finance.services.budget_accounts import confirm_create_budget_account
from jarvis_finance.services.budget_categories import confirm_create_category, update_category
from jarvis_finance.services.budget_imports import confirm_transaction_candidate, list_transaction_candidates, preview_transaction_candidate, seed_transaction_candidates_from_csv_text, seed_transaction_candidates_from_rows
from jarvis_finance.services.budget_overview import get_budget_status_by_category, get_income_status
from jarvis_finance.services.budget_plans import confirm_budget_plan_item
from jarvis_finance.services.budget_seed_review import convert_unclean_range, ignore_seed_candidate, list_seed_candidates, reopen_seed_candidate, seed_budget_candidates_from_rows
from jarvis_finance.services.budget_transactions import confirm_budget_transaction, list_budget_transactions, update_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 test_phase14_transaction_candidate_schema_and_csv_dry_run_no_productive_booking() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    before = conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0]
    csv_text = "Datum;Filiale;Artikel;Umsatz\n13.05.2026;M Windisch;Test Einkauf;12.30\n"
    stats = seed_transaction_candidates_from_csv_text(conn, csv_text, source_file_label="migros_test.csv")
    assert stats["candidate_count"] == 1
    assert conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0] == before
    candidate = list_transaction_candidates(conn)[0]
    assert candidate["proposed_category_name"]
    preview = preview_transaction_candidate(conn, candidate["transaction_candidate_id"], {"account_id": account, "transaction_type": "expense"})
    assert preview["payload"]["account_id"] == account


def test_phase14_confirm_transaction_candidate_writes_transaction_and_audit() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    cat = confirm_create_category(conn, {"name": "Essen", "category_type": "expense"})["entity_id"]
    seed_transaction_candidates_from_rows(conn, [{"Datum": "13.05.2026", "Artikel": "Migros Einkauf", "Umsatz": "12.30"}], source_file_label="migros.csv")
    candidate = list_transaction_candidates(conn)[0]
    result = confirm_transaction_candidate(conn, candidate["transaction_candidate_id"], {"account_id": account, "category_id": cat, "transaction_type": "expense"})
    assert result["status"] == "confirmed"
    assert conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0] == 1
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type='budget_transaction_candidate'").fetchone()[0] == 1


def test_phase14_budget_status_month_year_forecast_and_traffic_lights_for_expense_and_income() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    expense_cat = confirm_create_category(conn, {"name": "Haushalt", "category_type": "expense"})["entity_id"]
    income_cat = confirm_create_category(conn, {"name": "Lohn", "category_type": "income"})["entity_id"]
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": expense_cat, "name": "Haushalt Plan", "monthly_amount_chf": "100.00", "cadence": "monthly", "source_type": "manual"})
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": income_cat, "name": "Lohn Plan", "annual_amount_chf": "1200.00", "cadence": "annual", "source_type": "manual"})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-01-10", "description": "Haushalt Ist", "amount_original": "95.00", "currency_original": "CHF", "category_id": expense_cat})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "income", "transaction_date": "2026-01-10", "description": "Lohn Ist", "amount_original": "100.00", "currency_original": "CHF", "category_id": income_cat})
    rows = get_budget_status_by_category(conn, year="2026")
    expense = next(r for r in rows if r["category_id"] == expense_cat)
    income = next(r for r in rows if r["category_id"] == income_cat)
    assert expense["budget_month"] == "100.00"
    assert expense["budget_year"] == "1200.00"
    assert expense["status"] in {"Grün", "Gelb", "Rot", "Kritisch"}
    assert income["budget_month"] == "100.00"
    assert income["budget_year"] == "1200.00"
    assert income["status"] in {"Grün", "Gelb", "Rot"}
    summary = get_income_status(conn, year="2026")
    assert summary["categories"]


def test_phase14_row_click_backing_filters_and_transaction_update_audit_no_amount_overwrite() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    cat1 = confirm_create_category(conn, {"name": "Alt", "category_type": "expense"})["entity_id"]
    cat2 = confirm_create_category(conn, {"name": "Neu", "category_type": "expense"})["entity_id"]
    tx = confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-01-10", "description": "Merchant A", "amount_original": "20.00", "currency_original": "CHF", "category_id": cat1})["entity_id"]
    assert list_budget_transactions(conn, category_id=cat1)
    result = update_budget_transaction(conn, tx, {"category_id": cat2, "description": "Merchant B", "tag_names": ["Review"], "change_note": "test"})
    assert result["status"] == "updated"
    assert not list_budget_transactions(conn, category_id=cat1)
    assert list_budget_transactions(conn, category_id=cat2, merchant="Merchant")
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type='budget_transaction' AND action='update'").fetchone()[0] == 1
    try:
        update_budget_transaction(conn, tx, {"amount_original": "99.00"})
    except Exception as exc:
        assert "adjustment" in str(exc).lower() or "reversal" in str(exc).lower()
    else:  # pragma: no cover
        raise AssertionError("amount overwrite must be rejected")


def test_phase14_category_update_and_unclean_conversion_to_income_and_transaction_candidate() -> None:
    conn = db()
    cat = confirm_create_category(conn, {"name": "Original", "category_type": "expense"})["entity_id"]
    result = update_category(conn, cat, {"name": "Umbenannt", "category_type": "neutral", "sort_order": 7})
    assert result["status"] == "updated"
    assert conn.execute("SELECT name, category_type FROM budget_categories WHERE category_id=?", (cat,)).fetchone()[0] == "Umbenannt"
    workbook = {"2026": [["Kategorie", "Position", "Jan", "Jahr"], ["", "Unklar", "", ""]]}
    seed_budget_candidates_from_rows(conn, workbook, source_file_label="unclean.xlsx")
    unclean = next(c for c in list_seed_candidates(conn) if c["candidate_type"] == "unclean_range")
    income = convert_unclean_range(conn, unclean["seed_candidate_id"], "income", {"proposed_name": "Sonstige Einnahmen", "proposed_amount_text": "10.00"})
    txcand = convert_unclean_range(conn, unclean["seed_candidate_id"], "transaction_candidate", {"description": "Migros", "amount": "10.00", "transaction_date": "2026-01-01"})
    assert income["status"] == "created"
    assert txcand["candidate_count"] == 1
    reopened = reopen_seed_candidate(conn, unclean["seed_candidate_id"])
    assert reopened["status"] == "pending"
    ignore_seed_candidate(conn, unclean["seed_candidate_id"], "done")
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type='budget_seed_candidate'").fetchone()[0] >= 3
