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_plans import confirm_budget_plan_item
from jarvis_finance.services.budget_transactions import confirm_budget_transaction
from jarvis_finance.services.budget_overview import get_budget_status_by_category
from jarvis_finance.services.budget_analytics_v2 import get_budget_dashboard_cockpit, get_budget_vs_actual_analysis
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 seed_budget(conn):
    account = confirm_create_budget_account(conn, {"name": "Haushalt", "account_type": "checking", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense", "color": "#22c55e", "icon": "basket", "sort_order": 10})["entity_id"]
    health = confirm_create_category(conn, {"name": "Gesundheit / Medizin", "category_type": "expense", "color": "#ef4444", "icon": "heart", "sort_order": 20})["entity_id"]
    salary = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income", "color": "#38bdf8", "icon": "wallet", "sort_order": 1})["entity_id"]
    confirm_budget_plan_item(conn, {"name": "Food Plan", "category_id": food, "plan_month": "2026-01", "monthly_amount_chf": "100.00", "cadence": "monthly", "source_type": "manual"})
    confirm_budget_plan_item(conn, {"name": "Health Plan", "category_id": health, "plan_month": "2026-01", "monthly_amount_chf": "50.00", "cadence": "monthly", "source_type": "manual"})
    confirm_budget_plan_item(conn, {"name": "Salary Plan", "category_id": salary, "plan_month": "2026-01", "monthly_amount_chf": "300.00", "cadence": "monthly", "source_type": "manual"})
    return account, food, health, salary


def seed_transactions(conn, account: str, food: str, health: str, salary: str) -> None:
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "income", "transaction_date": "2026-05-25", "description": "Lohn", "amount_original": "300.00", "currency_original": "CHF", "category_id": salary})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-05-03", "description": "Migros", "payee": "Migros", "amount_original": "120.00", "currency_original": "CHF", "category_id": food})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-05-10", "description": "Apotheke", "payee": "Apotheke", "amount_original": "40.00", "currency_original": "CHF", "category_id": health})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-02-10", "description": "Migros Feb", "amount_original": "80.00", "currency_original": "CHF", "category_id": food})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2025-05-10", "description": "Altjahr", "amount_original": "999.00", "currency_original": "CHF", "category_id": food})
    # Transfers and open candidates must not count as income/expense actuals.
    conn.execute("INSERT INTO budget_transactions(budget_transaction_id, account_id, category_id, transaction_date, transaction_type, description, amount_original, currency_original, amount_chf, fx_status, source_type, status, created_at, updated_at) VALUES ('tx_transfer', ?, NULL, '2026-05-11', 'transfer', 'Umbuchung', '777.00', 'CHF', '777.00', 'not_needed', 'manual', 'confirmed', '2026', '2026')", (account,))
    conn.execute("INSERT INTO budget_transaction_candidates(transaction_candidate_id, source_type, source_file_label, transaction_date, description, merchant, amount_original, currency_original, proposed_category_id, status, created_at, updated_at) VALUES ('cand_open','csv','x','2026-05-12','Candidate','Migros','888.00','CHF',?,'pending','2026','2026')", (food,))
    conn.execute("INSERT INTO budget_transaction_candidates(transaction_candidate_id, source_type, source_file_label, transaction_date, description, merchant, amount_original, currency_original, proposed_category_id, status, created_at, updated_at) VALUES ('cand_covered','csv','x','2026-05-13','Covered Migros','Migros','999.00','CHF',?,'covered_by_migros','2026','2026')", (food,))
    conn.commit()


def test_budget_dashboard_cockpit_is_monthly_and_excludes_candidates_transfers_and_2025(
    budget_reference_date,
) -> None:
    conn = db()
    account, food, health, salary = seed_budget(conn)
    seed_transactions(conn, account, food, health, salary)
    result = get_budget_dashboard_cockpit(conn, month="2026-05")
    assert result["purpose"] == "monthly_cockpit"
    assert result["month"] == "2026-05"
    assert result["kpis"]["income_current_month_chf"] == "300.00"
    assert result["kpis"]["expense_current_month_chf"] == "160.00"
    assert result["kpis"]["net_cashflow_current_month_chf"] == "140.00"
    assert result["kpis"]["budget_consumption_percent"] == "106.67"
    assert result["kpis"]["savings_rate_percent"] == "46.67"
    assert result["open_todos"]["review_candidates"] == 1
    assert all("888.00" not in str(section) and "999.00" not in str(section) and "777.00" not in str(section) for section in [result])
    assert result["top_expense_categories"][0]["category"] == "Essen & Haushalt"
    assert result["critical_categories"]
    assert result["monthly_cashflow"]["income_chf"] == "300.00"


def test_budget_status_rows_are_operational_category_control_with_monthly_detail(
    budget_reference_date,
) -> None:
    conn = db()
    account, food, health, salary = seed_budget(conn)
    seed_transactions(conn, account, food, health, salary)
    rows = get_budget_status_by_category(conn, year="2026")
    food_row = next(r for r in rows if r["category_id"] == food)
    assert food_row["purpose"] == "category_control"
    assert food_row["budget_month"] == "100.00"
    assert food_row["budget_year"] == "1200.00"
    assert food_row["actual_current_month"] == "120.00"
    assert food_row["actual_year_to_date"] == "200.00"
    assert len(food_row["monthly_analysis"]) == 12
    assert food_row["monthly_analysis"][4]["actual_chf"] == "120.00"
    assert "999.00" not in str(food_row)


def test_budget_vs_actual_analysis_is_separate_chart_viewmodel(budget_reference_date) -> None:
    conn = db()
    account, food, health, salary = seed_budget(conn)
    seed_transactions(conn, account, food, health, salary)
    result = get_budget_vs_actual_analysis(conn, year="2026")
    assert result["purpose"] == "plan_vs_reality_analysis"
    assert result["year"] == "2026"
    assert result["chart_library"] == "native-svg"
    assert result["empty_state"] is False
    assert result["budget_vs_actual_by_category"]
    assert result["top_overruns"][0]["category"] == "Essen & Haushalt"
    assert result["cashflow_by_month"][4]["month"] == "2026-05"
    assert result["cashflow_by_month"][4]["net_cashflow_chf"] == "140.00"
    assert result["category_donut"][0]["category"] == "Essen & Haushalt"
    assert result["trend_by_month"][4]["cumulative_net_chf"] == "60.00"
    assert "777.00" not in str(result)
    assert "888.00" not in str(result)
    assert "999.00" not in str(result)
