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_mark_candidates,
    list_transaction_candidates,
    preview_batch_confirm_candidates,
    seed_credit_card_candidates_from_rows,
)
from jarvis_finance.services.budget_transactions import list_budget_transactions
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_bulk(conn):
    account_id = confirm_create_budget_account(conn, {"name": "Review Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    food_id = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense"})["entity_id"]
    media_id = confirm_create_category(conn, {"name": "Elektronische Medien", "category_type": "expense"})["entity_id"]
    seed_credit_card_candidates_from_rows(
        conn,
        [
            {"Datum": "2026-05-01", "Beschreibung": "APPLE.COM/BILL", "Betrag": "9.90"},
            {"Datum": "2026-05-02", "Beschreibung": "GOOGLE STORAGE", "Betrag": "2.00"},
            {"Datum": "2026-05-03", "Beschreibung": "NETFLIX", "Betrag": "19.90"},
            {"Datum": "2026-05-04", "Beschreibung": "COVERED SOURCE", "Betrag": "5.00"},
        ],
        source_file_label="visa.csv",
        migros_covered=False,
    )
    rows = list_transaction_candidates(conn, include_reference=True)
    by_desc = {row["description"]: row["transaction_candidate_id"] for row in rows}
    conn.execute("UPDATE budget_transaction_candidates SET status='covered_by_source' WHERE transaction_candidate_id=?", (by_desc["COVERED SOURCE"],))
    conn.commit()
    return account_id, food_id, media_id, by_desc


def test_bulk_category_assignment_updates_only_editable_candidates_and_reports_partial_errors() -> None:
    conn = db()
    _account_id, _food_id, media_id, ids = setup_bulk(conn)

    result = batch_mark_candidates(
        conn,
        candidate_ids=[ids["APPLE.COM/BILL"], ids["GOOGLE STORAGE"], ids["COVERED SOURCE"]],
        action="category",
        category_id=media_id,
        budget_year="2026",
    )

    assert result["updated_count"] == 2
    assert result["skipped_count"] == 1
    assert result["errors"][0]["candidate_id"] == ids["COVERED SOURCE"]
    editable = conn.execute("SELECT proposed_category_id FROM budget_transaction_candidates WHERE transaction_candidate_id=?", (ids["APPLE.COM/BILL"],)).fetchone()
    blocked = conn.execute("SELECT proposed_category_id, status FROM budget_transaction_candidates WHERE transaction_candidate_id=?", (ids["COVERED SOURCE"],)).fetchone()
    assert editable["proposed_category_id"] == media_id
    assert blocked["proposed_category_id"] is None
    assert blocked["status"] == "covered_by_source"
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action IN ('batch_category_candidates','update_category')").fetchone()[0] >= 2


def test_bulk_confirm_preview_blocks_duplicates_covered_transfer_and_missing_categories() -> None:
    conn = db()
    account_id, _food_id, media_id, ids = setup_bulk(conn)
    batch_mark_candidates(conn, candidate_ids=[ids["APPLE.COM/BILL"]], action="category", category_id=media_id, budget_year="2026")
    conn.execute("UPDATE budget_transaction_candidates SET status='duplicate_candidate' WHERE transaction_candidate_id=?", (ids["GOOGLE STORAGE"],))
    conn.execute("UPDATE budget_transaction_candidates SET status='transfer_candidate', classification='transfer_candidate' WHERE transaction_candidate_id=?", (ids["NETFLIX"],))
    conn.commit()

    preview = preview_batch_confirm_candidates(
        conn,
        candidate_ids=[ids["APPLE.COM/BILL"], ids["GOOGLE STORAGE"], ids["NETFLIX"], ids["COVERED SOURCE"]],
        account_id=account_id,
        budget_year="2026",
    )

    assert preview["count"] == 1
    reasons = {item["candidate_id"]: item["reason"] for item in preview["conflicts"]}
    assert "duplicate" in reasons[ids["GOOGLE STORAGE"]]
    assert "transfer" in reasons[ids["NETFLIX"]]
    assert "covered_by_source" in reasons[ids["COVERED SOURCE"]]
    assert preview["blockers"]["duplicate_candidates"] == 1
    assert preview["blockers"]["transfer_candidates"] == 1
    assert preview["blockers"]["covered_by_source"] == 1
    assert preview["blockers"]["without_category"] == 0


def test_bulk_confirm_creates_transactions_sets_candidate_links_and_removes_from_open_review() -> None:
    conn = db()
    account_id, _food_id, media_id, ids = setup_bulk(conn)
    selected = [ids["APPLE.COM/BILL"], ids["GOOGLE STORAGE"]]
    batch_mark_candidates(conn, candidate_ids=selected, action="category", category_id=media_id, budget_year="2026")
    preview = preview_batch_confirm_candidates(conn, candidate_ids=selected, account_id=account_id, budget_year="2026")

    result = batch_mark_candidates(
        conn,
        candidate_ids=selected,
        action="confirm",
        account_id=account_id,
        preview_id=preview["preview_id"],
        budget_year="2026",
    )

    assert result["confirmed_count"] == 2
    assert result["skipped_count"] == 0
    rows = conn.execute("SELECT status, confirmed_transaction_id FROM budget_transaction_candidates WHERE transaction_candidate_id IN (?, ?) ORDER BY transaction_candidate_id", selected).fetchall()
    assert {row["status"] for row in rows} == {"confirmed"}
    assert all(row["confirmed_transaction_id"] for row in rows)
    assert len(list_budget_transactions(conn, transaction_type="expense", year="2026")) == 2
    open_ids = {row["transaction_candidate_id"] for row in list_transaction_candidates(conn)}
    assert not set(selected) & open_ids


def test_bulk_ignore_and_reopen_multiple_candidates_with_audit() -> None:
    conn = db()
    _account_id, _food_id, _media_id, ids = setup_bulk(conn)
    selected = [ids["APPLE.COM/BILL"], ids["GOOGLE STORAGE"]]

    ignored = batch_mark_candidates(conn, candidate_ids=selected, action="ignore", budget_year="2026")
    reopened = batch_mark_candidates(conn, candidate_ids=selected, action="reopen", budget_year="2026")

    assert ignored["ignored_count"] == 2
    assert reopened["reopened_count"] == 2
    statuses = {row["status"] for row in conn.execute("SELECT status FROM budget_transaction_candidates WHERE transaction_candidate_id IN (?, ?)", selected).fetchall()}
    assert statuses <= {"pending", "needs_review"}
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action IN ('batch_ignore_candidates','batch_reopen_candidates','ignore','reopen')").fetchone()[0] >= 4
