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_import_status import build_review_backlog_dashboard, mark_2025_candidates_as_reference
from jarvis_finance.services.budget_imports import confirm_transaction_candidate, list_transaction_candidates
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 category(conn, name="Essen & Haushalt") -> str:
    return confirm_create_category(conn, {"name": name, "category_type": "expense"})["entity_id"]


def account(conn) -> str:
    return confirm_create_budget_account(conn, {"name": "Budget", "account_type": "cash", "currency": "CHF"})["entity_id"]


def cand(conn, cid: str, *, merchant: str, source_type: str = "visa_credit_card", status: str = "pending", category_id: str | None = None, date: str = "2026-03-10", confidence: str = "0.70", classification: str = "expense", requires_review: int = 1) -> None:
    conn.execute(
        """
        INSERT INTO budget_transaction_candidates(
            transaction_candidate_id, source_file_label, source_row_or_range, source_type,
            transaction_date, description, merchant, amount_original, currency_original,
            proposed_category_id, proposed_category_name, confidence, requires_review, status,
            classification, review_reason, raw_fingerprint, created_at, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, '0.00', 'CHF', ?,
            CASE WHEN ? IS NULL THEN NULL ELSE 'Kategorie' END, ?, ?, ?, ?,
            'fixture', ?, '2026-01-01T00:00:00', '2026-01-01T00:00:00')
        """,
        (cid, f"{source_type}.csv", cid, source_type, date, merchant, merchant, category_id, category_id, confidence, requires_review, status, classification, cid),
    )
    conn.commit()


def test_2025_candidates_are_marked_reference_and_not_in_2026_review_or_confirmable() -> None:
    conn = db(); cat = category(conn); acct = account(conn)
    cand(conn, "old-open", merchant="Old Shop", date="2025-08-01", category_id=cat, status="pending")
    cand(conn, "new-open", merchant="New Shop", date="2026-02-01", category_id=cat, status="pending")

    result = mark_2025_candidates_as_reference(conn)

    assert result["marked_count"] == 1
    assert {r["transaction_candidate_id"] for r in list_transaction_candidates(conn, budget_year="2026")} == {"new-open"}
    assert {r["transaction_candidate_id"] for r in list_transaction_candidates(conn, tab="reference_2025", budget_year="2025", include_reference=True)} == {"old-open"}
    try:
        confirm_transaction_candidate(conn, "old-open", {"account_id": acct, "category_id": cat, "budget_year": "2026"})
    except Exception as exc:  # noqa: BLE001
        assert "Duplikat/covered/ignored" in str(exc) or "outside active budget year" in str(exc)
    else:  # pragma: no cover
        raise AssertionError("2025 reference candidate must not confirm normally")


def test_review_backlog_groups_without_category_migros_galaxus_bank_fixkosten_and_safe_groups() -> None:
    conn = db(); cat = category(conn, "Elektronik")
    cand(conn, "uncat-a", merchant="Apple", category_id=None, confidence="0.40")
    cand(conn, "uncat-b", merchant="Apple", category_id=None, confidence="0.55")
    cand(conn, "galaxus", merchant="Galaxus", category_id=None, classification="galaxus_review")
    cand(conn, "migros-bon", merchant="Migros", source_type="migros_receipt", category_id=cat, classification="migros_receipt_food_household", requires_review=0)
    cand(conn, "cc-migros", merchant="Migros", source_type="visa_credit_card", status="covered_by_migros", category_id=cat)
    cand(conn, "akb", merchant="AKB Transfer", source_type="akb_bank", category_id=None, classification="transfer_candidate")
    cand(conn, "raiff", merchant="Raiffeisen Lohn", source_type="raiffeisen_bank", category_id=None, classification="income_candidate")
    cand(conn, "netflix", merchant="Netflix", category_id=cat, confidence="0.95", requires_review=0)
    cand(conn, "dup", merchant="Netflix", category_id=cat, status="duplicate_candidate")
    conn.execute("INSERT INTO budget_recurring_payments(recurring_id,name,merchant_name,category_id,expected_amount_text,currency,frequency,recurring_type,status,source,confidence,created_at,updated_at) VALUES ('rec1','Netflix','Netflix',?,'0.00','CHF','monthly','subscription','candidate','detected','0.90','2026-01-01','2026-01-01')", (cat,))
    conn.commit()

    dashboard = build_review_backlog_dashboard(conn)

    assert dashboard["header"]["candidates_without_category"] >= 4
    assert dashboard["groups"]["without_category"]["merchant_groups"][0]["count"] == 2
    assert dashboard["groups"]["galaxus_digitec"]["count"] == 1
    assert dashboard["groups"]["migros_bons"]["count"] == 1
    assert dashboard["groups"]["akb_raiffeisen"]["akb_count"] == 1
    assert dashboard["groups"]["akb_raiffeisen"]["raiffeisen_count"] == 1
    assert dashboard["groups"]["duplicates"]["count"] == 1
    assert dashboard["groups"]["fixkosten"]["count"] == 1
    safe_ids = set(dashboard["confirm_groups"]["safe_subscriptions"]["candidate_ids"])
    assert "netflix" in safe_ids
    assert "dup" not in safe_ids
    assert "cc-migros" not in safe_ids
    assert dashboard["confirm_groups"]["migros_bons"]["candidate_ids"] == ["migros-bon"]
    assert "amount" not in repr(dashboard).lower()


def test_review_backlog_is_read_only_and_does_not_create_bookings() -> None:
    conn = db(); cat = category(conn)
    cand(conn, "safe", merchant="OpenAI ChatGPT", category_id=cat, confidence="0.95", requires_review=0)
    before_candidates = conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates").fetchone()[0]
    before_tx = conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0]

    build_review_backlog_dashboard(conn)

    assert conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates").fetchone()[0] == before_candidates
    assert conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0] == before_tx
