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
from jarvis_finance.services.budget_cleanup import cleanup_uat12_budget_data
from jarvis_finance.services.budget_imports import (
    confirm_transaction_candidate,
    ignore_transaction_candidate,
    list_transaction_candidates,
    reopen_transaction_candidate,
    seed_transaction_candidates_from_rows,
    update_transaction_candidate_category,
)
from jarvis_finance.services.budget_overview import get_budget_status_by_category
from jarvis_finance.services.budget_transactions import 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_phase18_confirm_candidate_creates_productive_transaction_and_hides_candidate() -> 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": "Haushalt", "category_type": "expense"})["entity_id"]
    seed_transaction_candidates_from_rows(conn, [{"Datum": "2026-01-03", "Beschreibung": "Demo Händler", "Betrag": "10.00"}], source_file_label="phase18.csv")
    candidate = list_transaction_candidates(conn)[0]
    update_transaction_candidate_category(conn, candidate["transaction_candidate_id"], cat, "phase18")

    result = confirm_transaction_candidate(conn, candidate["transaction_candidate_id"], {"account_id": account})

    stored = conn.execute("SELECT * FROM budget_transaction_candidates WHERE transaction_candidate_id=?", (candidate["transaction_candidate_id"],)).fetchone()
    assert stored["status"] == "confirmed"
    assert stored["confirmed_transaction_id"] == result["entity_id"]
    assert stored["confirmed_at"]
    assert stored["confirmed_by"] == "user"
    assert not any(c["transaction_candidate_id"] == candidate["transaction_candidate_id"] for c in list_transaction_candidates(conn))

    txs = list_budget_transactions(conn, transaction_type="expense", status="confirmed")
    assert [tx["budget_transaction_id"] for tx in txs] == [result["entity_id"]]
    tx = txs[0]
    assert tx["source_type"] == "import_candidate"
    assert tx["source_candidate_id"] == candidate["transaction_candidate_id"]
    assert tx["category_id"] == cat
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action IN ('candidate_confirmed','budget_transaction_created')").fetchone()[0] >= 2


def test_phase18_confirm_validates_required_fields() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    seed_transaction_candidates_from_rows(conn, [{"Datum": "2026-01-03", "Beschreibung": "Demo Händler", "Betrag": "10.00"}], source_file_label="phase18.csv")
    candidate = list_transaction_candidates(conn)[0]
    for payload in [{"account_id": account}, {"category_id": "missing"}, {"account_id": "missing", "category_id": "missing"}]:
        try:
            confirm_transaction_candidate(conn, candidate["transaction_candidate_id"], payload)
        except Exception as exc:
            assert "category" in str(exc).lower() or "account" in str(exc).lower()
        else:  # pragma: no cover
            raise AssertionError("invalid confirm must fail")


def test_phase18_effective_expenses_exclude_test_seed_candidates_and_archive_uat12() -> None:
    conn = db()
    uat_account = confirm_create_budget_account(conn, {"name": "UAT12-20260516231955 Konto A", "account_type": "cash", "currency": "CHF"})["entity_id"]
    uat_cat = confirm_create_category(conn, {"name": "UAT12-20260516231955 Kategorie", "category_type": "expense"})["entity_id"]
    good_account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    good_cat = confirm_create_category(conn, {"name": "Haushalt", "category_type": "expense"})["entity_id"]
    conn.execute("INSERT INTO budget_transactions(budget_transaction_id, account_id, transaction_type, transaction_date, description, amount_original, currency_original, fx_rate_to_chf, amount_chf, fx_status, category_id, status, source_type, notes, created_at, updated_at) VALUES ('uat_tx', ?, 'expense', '2026-01-01', 'UAT12-20260516231955 Ausgabe', '1.00', 'CHF', '1', '1.00', 'not_needed', ?, 'confirmed', 'manual', 'UAT12', '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')", (uat_account, uat_cat))
    conn.execute("INSERT INTO budget_transactions(budget_transaction_id, account_id, transaction_type, transaction_date, description, amount_original, currency_original, fx_rate_to_chf, amount_chf, fx_status, category_id, status, source_type, notes, created_at, updated_at) VALUES ('good_tx', ?, 'expense', '2026-01-01', 'Produktiv', '1.00', 'CHF', '1', '1.00', 'not_needed', ?, 'confirmed', 'manual', NULL, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')", (good_account, good_cat))
    conn.commit()

    dry = cleanup_uat12_budget_data(conn, dry_run=True)
    assert dry["accounts"] == 1
    assert dry["categories"] == 1
    assert dry["transactions"] == 1
    result = cleanup_uat12_budget_data(conn, dry_run=False)
    assert result["archived_transactions"] == 1
    visible = list_budget_transactions(conn, transaction_type="expense", status="confirmed")
    assert [tx["budget_transaction_id"] for tx in visible] == ["good_tx"]
    assert "UAT12" not in str(visible)


def test_phase18_category_change_audit_and_budget_status_row_click_backing() -> None:
    conn = db()
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    cat_a = confirm_create_category(conn, {"name": "Alt", "category_type": "expense"})["entity_id"]
    cat_b = confirm_create_category(conn, {"name": "Neu", "category_type": "expense"})["entity_id"]
    seed_transaction_candidates_from_rows(conn, [{"Datum": "2026-01-03", "Beschreibung": "Demo Händler", "Betrag": "10.00"}], source_file_label="phase18.csv")
    cand = list_transaction_candidates(conn)[0]
    update_transaction_candidate_category(conn, cand["transaction_candidate_id"], cat_a, "phase18")
    tx_id = confirm_transaction_candidate(conn, cand["transaction_candidate_id"], {"account_id": account})["entity_id"]
    update_budget_transaction(conn, tx_id, {"category_id": cat_b, "change_note": "phase18 recategorise"})

    assert list_budget_transactions(conn, transaction_type="expense", status="confirmed", category_id=cat_b)
    rows = get_budget_status_by_category(conn, year="2026")
    row_b = next(r for r in rows if r["category_id"] == cat_b)
    assert row_b["actual_year_to_date"] != "0.00"
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type='budget_transaction' AND action='update'").fetchone()[0] >= 1


def test_phase18_ignore_reopen_keeps_candidate_visible_again() -> None:
    conn = db()
    seed_transaction_candidates_from_rows(conn, [{"Datum": "2026-01-03", "Beschreibung": "Demo Händler", "Betrag": "10.00"}], source_file_label="phase18.csv")
    cid = list_transaction_candidates(conn)[0]["transaction_candidate_id"]
    ignore_transaction_candidate(conn, cid, "phase18 ignore")
    assert not list_transaction_candidates(conn)
    reopened = reopen_transaction_candidate(conn, cid, "phase18 reopen")
    assert reopened["status"] in {"pending", "needs_review"}
    assert any(c["transaction_candidate_id"] == cid for c in list_transaction_candidates(conn))
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type='budget_transaction_candidate' AND action IN ('ignore','reopen')").fetchone()[0] == 2
