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_imports import (
    batch_confirm_safe_candidates,
    cleanup_migros_article_candidates,
    create_candidate_split,
    list_transaction_candidates,
    mark_candidate_transfer,
    seed_bank_transfer_candidates_from_rows,
    seed_credit_card_candidates_from_rows,
    seed_migros_candidates_from_rows,
    update_transaction_candidate_category,
)
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 add_categories(conn):
    return {
        "abos": confirm_create_category(conn, {"name": "Abos", "category_type": "expense"})["entity_id"],
        "food": confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense"})["entity_id"],
        "income": confirm_create_category(conn, {"name": "Lohn", "category_type": "income"})["entity_id"],
        "other": confirm_create_category(conn, {"name": "Sonstiges", "category_type": "expense"})["entity_id"],
    }


def test_migros_article_rows_are_aggregated_to_receipts_with_line_items_and_threshold_rules() -> None:
    conn = db(); cats = add_categories(conn)
    rows = [
        {"Datum": "01.05.2026", "Zeit": "10:00", "Filiale": "M Test", "Kasse": "1", "Transaktion": "A", "Artikel": "Brot", "Umsatz": "12.00"},
        {"Datum": "01.05.2026", "Zeit": "10:00", "Filiale": "M Test", "Kasse": "1", "Transaktion": "A", "Artikel": "Milch", "Umsatz": "9.00"},
        {"Datum": "02.05.2026", "Zeit": "11:00", "Filiale": "M Test", "Kasse": "2", "Transaktion": "B", "Artikel": "Haushalt", "Umsatz": "40.00"},
        {"Datum": "02.05.2026", "Zeit": "11:00", "Filiale": "M Test", "Kasse": "2", "Transaktion": "B", "Artikel": "Geschenk", "Umsatz": "20.00"},
    ]
    stats = seed_migros_candidates_from_rows(conn, rows, source_file_label="migros.csv", auto_category_id=cats["food"], threshold_chf="50.00")
    assert stats["receipt_candidate_count"] == 2
    assert stats["line_item_count"] == 4
    assert stats["auto_assigned_count"] == 2
    assert stats["review_required_count"] == 0
    candidates = list_transaction_candidates(conn)
    assert len(candidates) == 2
    assert all(c["source_type"] == "migros_receipt" for c in candidates)
    assert all("Brot" not in c["description"] and "Milch" not in c["description"] for c in candidates)
    small = next(c for c in candidates if c["description"].startswith("Migros Bon"))
    assert all(c["requires_review"] is False for c in candidates)
    assert {c["proposed_category_id"] for c in candidates} == {cats["food"]}
    assert small["classification"] == "migros_receipt_food_household"
    assert conn.execute("SELECT COUNT(*) FROM budget_import_line_items").fetchone()[0] == 4


def test_credit_card_rules_subscriptions_galaxus_migros_covered_and_duplicate_detection() -> None:
    conn = db(); cats = add_categories(conn)
    account = confirm_create_budget_account(conn, {"name": "Karte", "account_type": "credit_card", "currency": "CHF"})["entity_id"]
    from jarvis_finance.services.budget_transactions import confirm_budget_transaction
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-05-04", "description": "NETFLIX", "amount_original": "19.90", "currency_original": "CHF", "category_id": cats["abos"]})
    rows = [
        {"Datum": "01.05.2026", "Beschreibung": "APPLE.COM/BILL", "Betrag": "9.90"},
        {"Datum": "02.05.2026", "Beschreibung": "Galaxus Zürich", "Betrag": "120.00"},
        {"Datum": "03.05.2026", "Beschreibung": "MIGROS MMM", "Betrag": "42.00"},
        {"Datum": "04.05.2026", "Beschreibung": "NETFLIX", "Betrag": "19.90"},
    ]
    stats = seed_credit_card_candidates_from_rows(conn, rows, source_file_label="card.csv", subscription_category_id=cats["abos"], migros_covered=True)
    assert stats["candidate_count"] == 4
    assert stats["subscription_auto_count"] == 2
    assert stats["galaxus_review_count"] == 1
    assert stats["covered_by_migros_count"] == 1
    assert stats["possible_duplicate_count"] == 1
    by_desc = {c["description"]: c for c in list_transaction_candidates(conn)}
    duplicate_by_desc = {c["description"]: c for c in list_transaction_candidates(conn, tab="duplicates")}
    covered_by_desc = {c["description"]: c for c in list_transaction_candidates(conn, status="covered_by_migros")}
    assert by_desc["APPLE.COM/BILL"]["proposed_category_id"] == cats["abos"]
    assert by_desc["Galaxus Zürich"]["status"] == "needs_review"
    assert covered_by_desc["MIGROS MMM"]["status"] == "covered_by_migros"
    assert duplicate_by_desc["NETFLIX"]["status"] == "duplicate"
    assert "NETFLIX" not in by_desc


def test_bank_income_internal_transfer_and_unclear_payment_classification() -> None:
    conn = db(); cats = add_categories(conn)
    rows = [
        {"Datum": "01.05.2026", "Beschreibung": "Lohn Arbeitgeber", "Betrag": "5000.00", "Konto": "AKB Melanie"},
        {"Datum": "02.05.2026", "Beschreibung": "Übertrag an Raiffeisen Marcel", "Betrag": "-1000.00", "Konto": "AKB Melanie"},
        {"Datum": "03.05.2026", "Beschreibung": "Gutschrift von AKB Melanie", "Betrag": "1000.00", "Konto": "Raiffeisen Marcel"},
        {"Datum": "04.05.2026", "Beschreibung": "Unklare Zahlung", "Betrag": "-77.00", "Konto": "AKB Melanie"},
    ]
    stats = seed_bank_transfer_candidates_from_rows(conn, rows, source_file_label="bank.csv", income_category_id=cats["income"])
    assert stats["candidate_count"] == 4
    assert stats["income_candidate_count"] == 1
    assert stats["transfer_candidate_count"] == 2
    assert stats["review_required_count"] == 1
    candidates = list_transaction_candidates(conn)
    transfer_candidates = list_transaction_candidates(conn, tab="transfers", include_reference=True)
    assert len([c for c in candidates if c["classification"] == "income_candidate"]) == 1
    assert len([c for c in transfer_candidates if c["classification"] == "transfer_candidate"]) == 2
    assert next(c for c in candidates if c["description"] == "Unklare Zahlung")["status"] == "needs_review"


def test_review_actions_category_split_ignore_transfer_and_batch_confirm_safe_candidates() -> None:
    conn = db(); cats = add_categories(conn)
    account = confirm_create_budget_account(conn, {"name": "Budget", "account_type": "cash", "currency": "CHF"})["entity_id"]
    seed_credit_card_candidates_from_rows(conn, [{"Datum": "01.05.2026", "Beschreibung": "APPLE", "Betrag": "9.90"}], source_file_label="card.csv", subscription_category_id=cats["abos"])
    seed_migros_candidates_from_rows(conn, [{"Datum": "02.05.2026", "Zeit": "10:00", "Filiale": "M", "Kasse": "1", "Transaktion": "A", "Artikel": "X", "Umsatz": "12.00"}], source_file_label="migros.csv", auto_category_id=cats["food"])
    cid = list_transaction_candidates(conn)[0]["transaction_candidate_id"]
    assert update_transaction_candidate_category(conn, cid, cats["other"], note="review") ["status"] == "updated"
    assert create_candidate_split(conn, cid, [{"category_id": cats["food"], "amount_original": "5.00"}, {"category_id": cats["other"], "amount_original": "4.90"}])["split_count"] == 2
    assert mark_candidate_transfer(conn, cid, note="own transfer")["status"] == "transfer_candidate"
    safe = batch_confirm_safe_candidates(conn, account_id=account, candidate_ids=[c["transaction_candidate_id"] for c in list_transaction_candidates(conn) if c["classification"] in {"subscription_auto", "migros_auto_under_threshold", "migros_receipt_food_household"}])
    assert safe["confirmed_count"] >= 1
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type IN ('budget_transaction_candidate','budget_transaction_candidate_split')").fetchone()[0] >= 3


def test_cleanup_migros_article_candidates_marks_superseded_and_audits() -> None:
    conn = db(); add_categories(conn)
    from jarvis_finance.services.budget_imports import seed_transaction_candidates_from_rows
    seed_transaction_candidates_from_rows(conn, [{"Datum": "01.05.2026", "Filiale": "M", "Artikel": "Brot", "Umsatz": "1.00"}], source_file_label="migros_old.csv")
    stats = cleanup_migros_article_candidates(conn, dry_run=False)
    assert stats["article_candidate_count"] == 1
    assert stats["superseded_count"] == 1
    row = list_transaction_candidates(conn, status="superseded")[0]
    assert row["status"] == "superseded"
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action='cleanup_migros_article_candidates'").fetchone()[0] == 1
