from __future__ import annotations

import pytest
from fastapi import HTTPException

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 (
    apply_merchant_to_open_candidates,
    create_merchant,
    create_merchant_alias,
    detect_duplicate_candidates,
    list_merchants,
    list_transaction_candidates,
    merge_merchants,
    create_budget_rule,
    list_budget_rules,
    update_budget_rule,
    prioritize_budget_rule,
    test_budget_rule as service_test_budget_rule,
    apply_budget_rules_to_open_candidates,
    seed_credit_card_candidates_from_rows,
    seed_transaction_candidates_from_rows,
    update_merchant_category,
    confirm_transaction_candidate,
    confirm_duplicate_override,
)
from jarvis_finance.services.budget_transactions import confirm_budget_transaction, 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_base(conn):
    account = confirm_create_budget_account(conn, {"name": "Review Konto", "account_type": "cash", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense"})["entity_id"]
    subs = confirm_create_category(conn, {"name": "Abos", "category_type": "expense"})["entity_id"]
    health = confirm_create_category(conn, {"name": "Gesundheit / Medizin", "category_type": "expense"})["entity_id"]
    return account, food, subs, health


def test_merchant_alias_detects_candidates_sets_default_category_and_audits() -> None:
    conn = db(); _account, _food, subs, _health = setup_base(conn)
    merchant = create_merchant(conn, {"display_name": "Apple", "default_category_id": subs, "notes": "subscriptions"})
    alias = create_merchant_alias(conn, merchant["merchant_id"], {"pattern": "APPLE.COM", "match_type": "contains", "source_type": "credit_card_csv", "priority": 5})
    seed_credit_card_candidates_from_rows(conn, [{"Datum": "2026-03-01", "Beschreibung": "APPLE.COM/BILL", "Betrag": "9.90"}], source_file_label="card.csv")

    applied = apply_merchant_to_open_candidates(conn, merchant["merchant_id"])
    row = list_transaction_candidates(conn, merchant="apple")[0]

    assert alias["status"] == "created"
    assert applied["updated_count"] == 1
    assert row["merchant_id"] == merchant["merchant_id"]
    assert row["merchant_display_name"] == "Apple"
    assert row["proposed_category_id"] == subs
    assert row["status"] == "auto_categorized"
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_type IN ('budget_merchant','budget_merchant_alias')").fetchone()[0] >= 2


def test_merchant_merge_repoints_aliases_and_candidates_with_audit() -> None:
    conn = db(); _account, _food, subs, _health = setup_base(conn)
    target = create_merchant(conn, {"display_name": "Galaxus", "default_category_id": subs})
    source = create_merchant(conn, {"display_name": "Digitec", "default_category_id": subs})
    create_merchant_alias(conn, source["merchant_id"], {"pattern": "DIGITEC", "match_type": "contains"})
    seed_credit_card_candidates_from_rows(conn, [{"Datum": "2026-03-02", "Beschreibung": "DIGITEC GALAXUS", "Betrag": "20.00"}], source_file_label="card.csv")
    apply_merchant_to_open_candidates(conn, source["merchant_id"])

    merged = merge_merchants(conn, source["merchant_id"], target["merchant_id"])

    assert merged["status"] == "merged"
    assert list_merchants(conn, include_inactive=True)[0]["display_name"] in {"Galaxus", "Digitec"}
    assert conn.execute("SELECT COUNT(*) FROM budget_merchant_aliases WHERE merchant_id=?", (target["merchant_id"],)).fetchone()[0] == 1
    assert conn.execute("SELECT merchant_id FROM budget_transaction_candidates WHERE description LIKE '%DIGITEC%'").fetchone()["merchant_id"] == target["merchant_id"]
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action='budget_merchant_merged'").fetchone()[0] == 1


def test_duplicate_detection_flags_near_date_same_amount_similar_merchant_and_protects_covered_source() -> None:
    conn = db(); account, food, _subs, _health = setup_base(conn)
    confirmed = confirm_budget_transaction(conn, {
        "account_id": account,
        "transaction_type": "expense",
        "transaction_date": "2026-04-10",
        "description": "Migros Einkauf",
        "payee": "Migros",
        "amount_original": "42.00",
        "currency_original": "CHF",
        "category_id": food,
        "source_type": "manual",
    })
    seed_credit_card_candidates_from_rows(conn, [
        {"Datum": "2026-04-12", "Beschreibung": "MIGROS MMM", "Betrag": "42.00"},
        {"Datum": "2026-04-12", "Beschreibung": "MIGROS covered", "Betrag": "42.00"},
    ], source_file_label="card.csv", migros_covered=False)
    covered_id = [r for r in list_transaction_candidates(conn, include_reference=True) if "covered" in r["description"]][0]["transaction_candidate_id"]
    conn.execute("UPDATE budget_transaction_candidates SET status='covered_by_source' WHERE transaction_candidate_id=?", (covered_id,))
    conn.commit()

    result = detect_duplicate_candidates(conn)
    rows = list_transaction_candidates(conn, tab="duplicates")

    assert result["duplicate_count"] >= 1
    assert any(r["duplicate_of_transaction_id"] == confirmed["entity_id"] and r["status"] == "duplicate_candidate" for r in rows)
    assert conn.execute("SELECT status FROM budget_transaction_candidates WHERE transaction_candidate_id=?", (covered_id,)).fetchone()["status"] == "covered_by_source"

    dup = rows[0]
    with pytest.raises(HTTPException):
        confirm_transaction_candidate(conn, dup["transaction_candidate_id"], {"account_id": account, "budget_year": "2026"})
    confirmed_anyway = confirm_duplicate_override(conn, dup["transaction_candidate_id"], {"account_id": account, "category_id": food, "budget_year": "2026", "reason": "Bewusst separate Ausgabe trotz Duplikat-Treffer"})
    assert confirmed_anyway["status"] == "confirmed"


def test_rule_manager_updates_prioritizes_tests_and_applies_without_booking() -> None:
    conn = db(); _account, food, subs, _health = setup_base(conn)
    seed_credit_card_candidates_from_rows(conn, [{"Datum": "2026-05-01", "Beschreibung": "NETFLIX.COM", "Betrag": "19.90"}], source_file_label="card.csv", migros_covered=False)
    rule = create_budget_rule(conn, {"merchant_contains": "Netflix", "category_id": food, "target_status": "review", "priority": 50, "confidence": "0.75"})

    updated = update_budget_rule(conn, rule["rule_id"], {"category_id": subs, "target_status": "auto", "confidence": "0.91", "notes": "known subscription"})
    prioritized = prioritize_budget_rule(conn, rule["rule_id"], 5)
    preview = service_test_budget_rule(conn, rule["rule_id"])
    applied = apply_budget_rules_to_open_candidates(conn, rule["rule_id"])
    candidate = list_transaction_candidates(conn, tab="auto")[0]
    rules = list_budget_rules(conn)

    assert updated["status"] == "updated"
    assert prioritized["priority"] == 5
    assert preview["match_count"] == 1
    assert preview["would_set_category"] == subs
    assert preview["confidence"] == "0.91"
    assert applied["productive_transaction_count"] == 0
    assert candidate["proposed_category_id"] == subs
    assert candidate["status"] == "auto_categorized"
    assert rules[0]["priority"] == 5
    assert conn.execute("SELECT COUNT(*) FROM budget_transactions").fetchone()[0] == 0
