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_data_explorer import (
    get_budget_data_explorer,
    get_budget_deviation_analysis,
    get_category_analysis_v2,
    get_merchant_analysis,
)
from jarvis_finance.services.budget_imports import seed_transaction_candidates_from_rows
from jarvis_finance.services.budget_plans import confirm_budget_plan_item
from jarvis_finance.services.budget_transactions import confirm_budget_transaction, confirm_transfer
from jarvis_finance.services.budget_analytics import get_monthly_comparison
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(conn):
    account = confirm_create_budget_account(conn, {"name": "Haushalt", "account_type": "checking", "currency": "CHF"})["entity_id"]
    other = confirm_create_budget_account(conn, {"name": "Sparen", "account_type": "savings", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense"})["entity_id"]
    health = confirm_create_category(conn, {"name": "Gesundheit", "category_type": "expense"})["entity_id"]
    income = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income"})["entity_id"]
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": food, "name": "Food Budget", "monthly_amount_chf": "50.00", "source_type": "manual"})
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": health, "name": "Health Fixed", "monthly_amount_chf": "20.00", "source_type": "manual", "is_fixed_cost": True})
    return account, other, food, health, income


def tx(conn, account, tx_type, date, desc, amount, category, payee=""):
    return confirm_budget_transaction(conn, {
        "account_id": account,
        "transaction_type": tx_type,
        "transaction_date": date,
        "description": desc,
        "payee": payee or desc,
        "amount_original": amount,
        "currency_original": "CHF",
        "category_id": category,
        "source_type": "manual",
    })["entity_id"]


def test_data_explorer_counts_only_confirmed_income_expense_and_excludes_candidates_transfers() -> None:
    conn = db()
    account, other, food, _health, income = setup(conn)
    tx(conn, account, "income", "2026-05-01", "Lohn", "100.00", income, "ERNE")
    tx(conn, account, "expense", "2026-05-02", "Einkauf", "30.00", food, "Migros")
    tx(conn, account, "expense", "2026-05-03", "Restaurant", "20.00", food, "Restaurant")
    confirm_transfer(conn, {"from_account_id": account, "to_account_id": other, "transaction_date": "2026-05-04", "description": "AKB Raiffeisen", "amount_original": "999.00", "currency_original": "CHF"})
    seed_transaction_candidates_from_rows(conn, [{"date": "2026-05-05", "description": "Candidate", "amount": "123.00"}], source_file_label="candidate.csv")

    data = get_budget_data_explorer(conn, year="2026", month="2026-05", type="both")

    assert data["purpose"] == "budget_data_explorer"
    assert data["kpis"]["income_sum_chf"] == "100.00"
    assert data["kpis"]["expense_sum_chf"] == "50.00"
    assert data["kpis"]["net_cashflow_chf"] == "50.00"
    assert data["kpis"]["transaction_count"] == 3
    assert len(data["transactions"]) == 3
    assert all(row["transaction_type"] in {"income", "expense"} for row in data["transactions"])
    assert data["export"]["target"] == "runtime_exports_only"


def test_data_explorer_filters_and_merchant_analysis() -> None:
    conn = db()
    account, _other, food, _health, income = setup(conn)
    tx(conn, account, "income", "2026-05-01", "Lohn", "100.00", income, "ERNE")
    tx(conn, account, "expense", "2026-05-02", "Einkauf 1", "30.00", food, "Migros")
    tx(conn, account, "expense", "2026-04-02", "Einkauf alt", "10.00", food, "Migros")
    tx(conn, account, "expense", "2026-05-03", "Apple", "20.00", food, "Apple")

    filtered = get_budget_data_explorer(conn, year="2026", month="2026-05", type="expense", merchant="Migros")
    merchants = get_merchant_analysis(conn, year="2026", month="2026-05", type="expense")

    assert filtered["kpis"]["expense_sum_chf"] == "30.00"
    assert filtered["kpis"]["top_merchant"] == "Migros"
    migros = next(row for row in merchants["top_merchants"] if row["merchant"] == "Migros")
    assert migros["transaction_count"] == 1
    assert migros["average_chf"] == "30.00"
    assert migros["change_vs_previous_month_chf"] == "20.00"


def test_category_analysis_top10_other_and_trend() -> None:
    conn = db()
    account, _other, food, _health, _income = setup(conn)
    tx(conn, account, "expense", "2026-01-02", "Migros", "10.00", food, "Migros")
    tx(conn, account, "expense", "2026-02-02", "Migros", "15.00", food, "Migros")

    data = get_category_analysis_v2(conn, year="2026", tx_type="expense")

    assert data["categories"][0]["category"] == "Essen & Haushalt"
    assert data["top10_other"][0]["amount_chf"] == "25.00"
    assert data["trend_by_top_category"][0]["month"] == "2026-01"


def test_monthly_comparison_has_savings_cumulative_and_detail_links() -> None:
    conn = db()
    account, _other, food, _health, income = setup(conn)
    tx(conn, account, "income", "2026-05-01", "Lohn", "100.00", income, "ERNE")
    tx(conn, account, "expense", "2026-05-02", "Einkauf", "25.00", food, "Migros")

    month = next(row for row in get_monthly_comparison(conn, year="2026")["months"] if row["month"] == "2026-05")

    assert month["income_chf"] == "100.00"
    assert month["expense_chf"] == "25.00"
    assert month["net_cashflow_chf"] == "75.00"
    assert month["savings_rate_percent"] == "75.00"
    assert month["cumulative_income_chf"] == "100.00"
    assert month["transactions_url"].startswith("/planning/budget/analysis/data-explorer")
    assert month["largest_transactions"]


def test_budget_deviation_analysis_reports_over_under_and_without_budget(budget_reference_date) -> None:
    conn = db()
    account, _other, food, health, _income = setup(conn)
    no_budget = confirm_create_category(conn, {"name": "Ohne Budget", "category_type": "expense"})["entity_id"]
    tx(conn, account, "expense", "2026-05-02", "Food", "80.00", food, "Migros")
    tx(conn, account, "expense", "2026-05-03", "Health", "5.00", health, "Apotheke")
    tx(conn, account, "expense", "2026-05-04", "NoPlan", "7.00", no_budget, "Other")

    data = get_budget_deviation_analysis(conn, year="2026")

    assert any(row["category"] == "Essen & Haushalt" for row in data["top_over_budget"])
    assert any(row["category"] == "Gesundheit" for row in data["top_under_budget"])
    assert any(row["category"] == "Ohne Budget" for row in data["categories_without_budget"])
