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_csv_imports import (
    detect_import_profile,
    import_budget_csv_text,
    parse_budget_csv_text,
)
from jarvis_finance.services.budget_imports import confirm_transaction_candidate, list_transaction_candidates, reclassify_bank_income_candidates
from jarvis_finance.services.budget_transactions import confirm_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 setup_base(conn):
    account = confirm_create_budget_account(conn, {"name": "Budget Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense"})["entity_id"]
    income = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income"})["entity_id"]
    return account, food, income


def test_import_profiles_detect_real_drive_csv_columns() -> None:
    assert detect_import_profile(["TransactionId", "CardId", "Date", "Amount", "Currency", "MerchantName"]) == "visa_credit_card"
    assert detect_import_profile(["Datum", "Zeit", "Filiale", "Kassennummer", "Transaktionsnummer", "Artikel", "Umsatz"]) == "migros_receipts"
    assert detect_import_profile(["IBAN", "Booked At", "Text", "Credit/Debit Amount", "Balance", "Valuta Date"]) == "raiffeisen_bank"
    assert detect_import_profile(["Buchung", "Valuta", "Buchungstext", "Belastung", "Gutschrift", "Saldo CHF"]) == "akb_bank"


def test_dry_run_parses_all_profiles_without_writing_candidates() -> None:
    conn = db(); _account, food, income = setup_base(conn)
    samples = {
        "visa_credit_card": "TransactionId,CardId,Date,ValutaDate,Amount,Currency,MerchantName,Details\nT1,C1,2026-01-03,2026-01-04,-12.34,CHF,APPLE.COM,Bill\n",
        "migros_receipts": "Datum;Zeit;Filiale;Kassennummer;Transaktionsnummer;Artikel;Menge;Aktion;Umsatz\n02.01.2026;10:00;Migros Test;1;99;Artikel A;1;;1.10\n02.01.2026;10:00;Migros Test;1;99;Artikel B;1;;2.20\n",
        "raiffeisen_bank": "IBAN;Booked At;Text;Credit/Debit Amount;Balance;Valuta Date\nCH00;2026-01-05;Lohn Marcel;123.45;1000;2026-01-05\n",
        "akb_bank": "Buchung;Valuta;Buchungstext;Belastung;Gutschrift;Saldo CHF\n05.01.2026;05.01.2026;Miete Verwaltung;88.00;;1000\n",
    }
    for profile, text in samples.items():
        result = import_budget_csv_text(conn, profile, text, source_file_label=f"{profile}.csv", dry_run=True, default_category_id=food, income_category_id=income)
        assert result["profile"] == profile
        assert result["dry_run"] is True
        assert result["parsed_rows"] >= 1
    assert conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates").fetchone()[0] == 0


def test_import_is_idempotent_and_marks_already_processed_rows() -> None:
    conn = db(); _account, food, _income = setup_base(conn)
    csv_text = "TransactionId,CardId,Date,Amount,Currency,MerchantName,Details\nTID-1,CARD-1,2026-02-01,-10.00,CHF,Netflix,Subscription\n"

    first = import_budget_csv_text(conn, "visa_credit_card", csv_text, source_file_label="visa.csv", dry_run=False, default_category_id=food)
    second = import_budget_csv_text(conn, "visa_credit_card", csv_text, source_file_label="visa.csv", dry_run=False, default_category_id=food)

    assert first["candidate_count"] == 1
    assert second["candidate_count"] == 0
    assert second["already_processed_count"] == 1
    assert conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates").fetchone()[0] == 1


def test_import_marks_existing_confirmed_transaction_as_duplicate_existing() -> None:
    conn = db(); account, food, _income = setup_base(conn)
    confirm_budget_transaction(conn, {
        "account_id": account,
        "transaction_type": "expense",
        "transaction_date": "2026-02-10",
        "description": "Manual Netflix",
        "amount_original": "10.00",
        "currency_original": "CHF",
        "category_id": food,
        "source_type": "manual",
    })
    csv_text = "TransactionId,CardId,Date,Amount,Currency,MerchantName,Details\nTID-2,CARD-1,2026-02-10,-10.00,CHF,Netflix,Subscription\n"

    result = import_budget_csv_text(conn, "visa_credit_card", csv_text, source_file_label="visa.csv", dry_run=False, default_category_id=food)
    row = list_transaction_candidates(conn, tab="duplicates", include_reference=True)[0]

    assert result["possible_duplicate_count"] == 1
    assert row["status"] == "duplicate_candidate"


def test_bank_import_classifies_transfers_true_wealth_credit_card_payment_and_income() -> None:
    conn = db(); _account, _food, income = setup_base(conn)
    csv_text = "IBAN;Booked At;Text;Credit/Debit Amount;Balance;Valuta Date\nCH1;2026-03-01;Übertrag an AKB eigenes Konto;-100.00;0;2026-03-01\nCH1;2026-03-02;True Wealth Einzahlung;-200.00;0;2026-03-02\nCH1;2026-03-03;VISA Kartenabrechnung;-300.00;0;2026-03-03\nCH1;2026-03-04;Lohn Marcel;400.00;0;2026-03-04\n"

    result = import_budget_csv_text(conn, "raiffeisen_bank", csv_text, source_file_label="raiffeisen.csv", dry_run=False, income_category_id=income)
    candidates = list_transaction_candidates(conn, include_reference=True, sort_by="date", sort_dir="asc")
    classifications = [c["classification"] for c in candidates]

    assert result["transfer_candidate_count"] >= 3
    assert "transfer_candidate" in classifications
    assert "investment_transfer" in classifications
    assert "credit_card_payment" in classifications
    assert "income_candidate" in classifications
    assert all(c["status"] != "confirmed" for c in candidates)


def test_migros_import_creates_one_receipt_candidate_and_detail_line_items() -> None:
    conn = db(); _account, food, _income = setup_base(conn)
    csv_text = "Datum;Zeit;Filiale;Kassennummer;Transaktionsnummer;Artikel;Menge;Aktion;Umsatz\n02.01.2026;10:00;Migros Test;1;99;Artikel A;1;;1.10\n02.01.2026;10:00;Migros Test;1;99;Artikel B;1;;2.20\n"

    result = import_budget_csv_text(conn, "migros_receipts", csv_text, source_file_label="migros.csv", dry_run=False, default_category_id=food)

    assert result["candidate_count"] == 1
    assert result["line_item_count"] == 2
    assert conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates WHERE source_type='migros_receipts'").fetchone()[0] == 1
    assert conn.execute("SELECT COUNT(*) FROM budget_import_line_items").fetchone()[0] == 2


def test_bank_income_recognition_patterns_and_negative_guards() -> None:
    conn = db(); _account, _food, income = setup_base(conn)
    csv_text = "IBAN;Booked At;Text;Credit/Debit Amount;Balance;Valuta Date\n" \
        "CH1;2026-04-01;ERNE AG Lohnzahlung;10.00;0;2026-04-01\n" \
        "CH1;2026-04-02;Musikschule Lohn Melanie;11.00;0;2026-04-02\n" \
        "CH1;2026-04-03;Gasser Bauunternehmen Salär;12.00;0;2026-04-03\n" \
        "CH1;2026-04-04;Bonus Gratifikation;13.00;0;2026-04-04\n" \
        "CH1;2026-04-05;Solarstrom Rückvergütung;14.00;0;2026-04-05\n" \
        "CH1;2026-04-06;AKB Umbuchung eigenes Konto;15.00;0;2026-04-06\n" \
        "CH1;2026-04-07;True Wealth Einzahlung;16.00;0;2026-04-07\n" \
        "CH1;2026-04-08;VISA Kartenabrechnung;17.00;0;2026-04-08\n"
    import_budget_csv_text(conn, "raiffeisen_bank", csv_text, source_file_label="raiffeisen.csv", dry_run=False, income_category_id=income)
    rows = list_transaction_candidates(conn, include_reference=True, sort_by="date", sort_dir="asc")
    by_desc = {r["description"]: r for r in rows}
    assert by_desc["ERNE AG Lohnzahlung"]["classification"] == "income_candidate"
    assert by_desc["Musikschule Lohn Melanie"]["classification"] == "income_candidate"
    assert by_desc["Gasser Bauunternehmen Salär"]["classification"] == "income_candidate"
    assert by_desc["Bonus Gratifikation"]["classification"] == "income_candidate"
    assert by_desc["Solarstrom Rückvergütung"]["classification"] == "income_candidate"
    assert by_desc["AKB Umbuchung eigenes Konto"]["classification"] == "transfer_candidate"
    assert by_desc["True Wealth Einzahlung"]["classification"] == "investment_transfer"
    assert by_desc["VISA Kartenabrechnung"]["classification"] == "credit_card_payment"


def test_reclassify_existing_akb_raiffeisen_candidates_without_confirming() -> None:
    conn = db(); account, _food, income = setup_base(conn)
    import_budget_csv_text(conn, "akb_bank", "Buchung;Valuta;Buchungstext;Belastung;Gutschrift;Saldo CHF\n01.04.2026;01.04.2026;ERNE Lohn;;10.00;0\n02.04.2026;02.04.2026;AKB Raiffeisen Übertrag;;10.00;0\n", source_file_label="akb.csv", dry_run=False, income_category_id=income)
    conn.execute("UPDATE budget_transaction_candidates SET status='needs_review', classification='bank_review', proposed_category_id=NULL, proposed_category_name=NULL")
    result = reclassify_bank_income_candidates(conn, budget_year="2026")
    rows = list_transaction_candidates(conn, include_reference=True, sort_by="date", sort_dir="asc")
    assert result["updated_count"] >= 2
    assert rows[0]["classification"] == "income_candidate"
    assert rows[0]["status"] == "pending"
    assert rows[1]["classification"] == "transfer_candidate"
    assert all(r["status"] != "confirmed" for r in rows)
    confirmed = confirm_transaction_candidate(conn, rows[0]["transaction_candidate_id"], {"account_id": account, "category_id": income, "transaction_type": "income", "budget_year": "2026"})
    assert confirmed["status"] == "confirmed"
    tx = conn.execute("SELECT transaction_type, source_type, source_candidate_id, status FROM budget_transactions WHERE budget_transaction_id=?", (confirmed["entity_id"],)).fetchone()
    assert dict(tx) == {"transaction_type": "income", "source_type": "import_candidate", "source_candidate_id": rows[0]["transaction_candidate_id"], "status": "confirmed"}
