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 list_transaction_candidates, update_transaction_candidate_category
from jarvis_finance.services.budget_monthly_import import (
    apply_rule_suggestion,
    create_rule_suggestion_from_candidate_change,
    get_import_history,
    get_monthly_import_dashboard,
    preview_drive_monthly_import,
    run_monthly_import_dry_run,
    scan_budget_drive_files,
)
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations, get_schema_version


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"]
    media = confirm_create_category(conn, {"name": "Elektronische Medien", "category_type": "expense"})["entity_id"]
    income = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income"})["entity_id"]
    return account, food, media, income


def test_monthly_import_drive_scan_detects_profiles_and_writes_dry_run_session() -> None:
    conn = db(); _account, food, _media, income = setup_base(conn)
    files = [
        {"id": "drv_visa", "name": "VISA_Mai.csv", "modifiedTime": "2026-05-18T10:00:00Z", "text": "TransactionId,CardId,Date,Amount,Currency,MerchantName,Details\nT1,C1,2026-05-01,-10.00,CHF,Netflix,Abo\n"},
        {"id": "drv_migros", "name": "Migros_Mai.csv", "modifiedTime": "2026-05-18T10:01:00Z", "text": "Datum;Zeit;Filiale;Kassennummer;Transaktionsnummer;Artikel;Menge;Aktion;Umsatz\n02.05.2026;10:00;Migros Test;1;99;Artikel;1;;1.00\n"},
        {"id": "drv_akb", "name": "AKB_Mai.csv", "modifiedTime": "2026-05-18T10:02:00Z", "text": "Buchung;Valuta;Buchungstext;Belastung;Gutschrift;Saldo CHF\n03.05.2026;03.05.2026;Lohn Marcel;;20.00;0\n"},
    ]

    scanned = scan_budget_drive_files(files)
    dry = run_monthly_import_dry_run(conn, files, default_category_id=food, income_category_id=income)
    history = get_import_history(conn)

    assert [f["profile"] for f in scanned["files"]] == ["visa_credit_card", "migros_receipts", "akb_bank"]
    assert dry["status"] == "dry_run"
    assert dry["files_total"] == 3
    assert dry["totals"]["would_create_candidate_count"] >= 3
    assert history[0]["status"] == "dry_run"
    assert sum(int(h["rows_total"] or 0) for h in history) >= 3
    assert conn.execute("SELECT COUNT(*) FROM budget_transaction_candidates").fetchone()[0] == 0


def test_monthly_import_confirm_creates_candidates_and_second_run_is_idempotent() -> None:
    conn = db(); _account, food, _media, income = setup_base(conn)
    files = [{"id": "drv_visa", "name": "VISA_Mai.csv", "text": "TransactionId,CardId,Date,Amount,Currency,MerchantName,Details\nT1,C1,2026-05-01,-10.00,CHF,Netflix,Abo\n"}]

    first = preview_drive_monthly_import(conn, files, dry_run=False, default_category_id=food, income_category_id=income)
    second = preview_drive_monthly_import(conn, files, dry_run=False, default_category_id=food, income_category_id=income)
    rows = list_transaction_candidates(conn, include_reference=True)

    assert first["status"] == "candidates_created"
    assert first["totals"]["candidate_count"] == 1
    assert second["totals"]["candidate_count"] == 0
    assert second["totals"]["already_processed_count"] == 1
    assert len(rows) == 1
    assert rows[0]["status"] in {"pending", "auto_categorized", "needs_review"}


def test_monthly_import_classification_dashboard_and_review_link_filters() -> None:
    conn = db(); _account, food, _media, income = setup_base(conn)
    files = [
        {"id": "drv_migros", "name": "Migros.csv", "text": "Datum;Zeit;Filiale;Kassennummer;Transaktionsnummer;Artikel;Menge;Aktion;Umsatz\n02.05.2026;10:00;Migros Test;1;99;Artikel;1;;1.00\n"},
        {"id": "drv_bank", "name": "Raiffeisen.csv", "text": "IBAN;Booked At;Text;Credit/Debit Amount;Balance;Valuta Date\nCH1;2026-05-03;True Wealth Einzahlung;-20.00;0;2026-05-03\nCH1;2026-05-04;VISA Kartenabrechnung;-30.00;0;2026-05-04\nCH1;2026-05-05;Lohn Marcel;40.00;0;2026-05-05\n"},
    ]
    result = preview_drive_monthly_import(conn, files, dry_run=False, default_category_id=food, income_category_id=income)
    dashboard = get_monthly_import_dashboard(conn)
    rows = list_transaction_candidates(conn, include_reference=True)
    classifications = {r["classification"] for r in rows}

    assert result["review_url"].startswith("/planning/budget/expenses/review?")
    assert "migros_receipt_food_household" in classifications
    assert "investment_transfer" in classifications
    assert "credit_card_payment" in classifications
    assert "income_candidate" in classifications
    assert dashboard["open_candidates_by_source"]
    assert dashboard["counts"]["income_candidate"] >= 1
    assert dashboard["counts"]["transfer_candidate"] >= 2


def test_rule_learning_suggests_rule_from_manual_category_change_and_applies_without_booking() -> None:
    conn = db(); _account, food, media, _income = setup_base(conn)
    files = [{"id": "drv_visa", "name": "VISA.csv", "text": "TransactionId,CardId,Date,Amount,Currency,MerchantName,Details\nT1,C1,2026-05-01,-10.00,CHF,NETFLIX,Abo\nT2,C1,2026-05-02,-10.00,CHF,NETFLIX,Abo 2\n"}]
    preview_drive_monthly_import(conn, files, dry_run=False, default_category_id=food)
    first = list_transaction_candidates(conn, include_reference=True)[0]
    update_transaction_candidate_category(conn, first["transaction_candidate_id"], media, "manual learning test")

    suggestion = create_rule_suggestion_from_candidate_change(conn, first["transaction_candidate_id"], category_id=media, source_scope="VISA")
    applied = apply_rule_suggestion(conn, suggestion["suggestion_id"], apply_to_open=True)
    rows = list_transaction_candidates(conn, include_reference=True)

    assert suggestion["match_type"] == "contains"
    assert suggestion["affected_open_candidate_count"] >= 1
    assert applied["status"] == "accepted"
    assert applied["updated_candidate_count"] >= 1
    assert conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0] == 0
    assert all(r["proposed_category_id"] == media for r in rows if "NETFLIX" in (r["merchant"] or r["description"]))


def test_schema_version_32_grocery_optimizer_tables_exist() -> None:
    conn = db()
    assert get_schema_version(conn) == 47
    for table in ["budget_import_sessions", "budget_rule_suggestions"]:
        assert conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table,)).fetchone()
