from __future__ import annotations

from decimal import Decimal

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_overview import get_budget_overview, get_budget_status_by_category, get_income_status
from jarvis_finance.services.budget_plans import (
    archive_budget_plan_item,
    confirm_budget_plan_item,
    confirm_income_plan_templates,
    confirm_update_income_plan_item,
    list_income_plan_items,
    preview_income_plan_templates,
    preview_update_income_plan_item,
)
from jarvis_finance.services.budget_transactions import confirm_budget_transaction
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 base(conn):
    account = confirm_create_budget_account(conn, {"name": "Budget 2026", "account_type": "cash", "currency": "CHF"})["entity_id"]
    food = confirm_create_category(conn, {"name": "Essen & Haushalt", "category_type": "expense", "sort_order": 10})["entity_id"]
    grocery = confirm_create_category(conn, {"name": "Lebensmittel", "category_type": "expense", "parent_category_id": food, "sort_order": 11})["entity_id"]
    health = confirm_create_category(conn, {"name": "Gesundheit / Medizin", "category_type": "expense", "sort_order": 40})["entity_id"]
    salary = confirm_create_category(conn, {"name": "Lohn Marcel", "category_type": "income", "sort_order": 10})["entity_id"]
    bonus = confirm_create_category(conn, {"name": "Gratifikation", "category_type": "income", "sort_order": 30})["entity_id"]
    unused = confirm_create_category(conn, {"name": "Haustiere", "category_type": "expense", "sort_order": 90})["entity_id"]
    return account, food, grocery, health, salary, bonus, unused


def test_phase111_budgetstatus_includes_actual_only_and_plan_only_but_not_empty_categories() -> None:
    conn = db()
    account, food, grocery, health, salary, _bonus, unused = base(conn)
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": health, "name": "Health plan", "monthly_amount_chf": "100.00", "cadence": "monthly"})
    confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": salary, "name": "Salary plan", "monthly_amount_chf": "1000.00", "cadence": "monthly"})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-01-10", "description": "Groceries", "amount_original": "42.25", "currency_original": "CHF", "category_id": food})
    rows = get_budget_status_by_category(conn, year="2026")
    by_id = {r["category_id"]: r for r in rows}
    assert food in by_id
    assert health in by_id
    assert salary in by_id
    assert unused not in by_id
    assert by_id[food]["budget_month"] is None
    assert by_id[food]["budget_year"] is None
    assert by_id[food]["status"] == "ohne Budget"
    assert by_id[food]["status_hint"] == "Ausgaben vorhanden, aber kein Budgetplan"
    assert by_id[food]["actual_year_to_date"] == "42.25"
    assert by_id[food]["monthly_actuals"]["01"] == "42.25"
    assert by_id[health]["status"] == "Noch keine Daten"


def test_phase111_subcategory_bookings_are_visible_and_parent_aggregates() -> None:
    conn = db()
    account, food, grocery, *_ = base(conn)
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "expense", "transaction_date": "2026-02-11", "description": "Sub Food", "amount_original": "25.75", "currency_original": "CHF", "category_id": grocery})
    rows = get_budget_status_by_category(conn, year="2026")
    by_id = {r["category_id"]: r for r in rows}
    assert grocery in by_id
    assert food in by_id
    assert by_id[grocery]["actual_year_to_date"] == "25.75"
    assert by_id[food]["actual_year_to_date"] == "25.75"
    assert by_id[food]["rollup_child_count"] >= 1
    assert by_id[food]["is_rollup"] is True


def test_phase111_income_plan_helpers_calculate_monthly_and_annual_and_overview_uses_income(
    budget_reference_date,
) -> None:
    conn = db()
    account, _food, _grocery, _health, salary, bonus, _unused = base(conn)
    monthly = confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": salary, "name": "Lohn Marcel", "monthly_amount_chf": "1000.00", "cadence": "monthly", "notes": "person=Marcel;income_type=Lohn"})
    annual = confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": bonus, "name": "Bonus", "annual_amount_chf": "1200.00", "cadence": "annual", "notes": "person=Haushalt;income_type=Gratifikation"})
    confirm_budget_transaction(conn, {"account_id": account, "transaction_type": "income", "transaction_date": "2026-05-25", "description": "Salary", "amount_original": "1000.00", "currency_original": "CHF", "category_id": salary})
    plans = list_income_plan_items(conn, year="2026")
    by_id = {p["plan_item_id"]: p for p in plans}
    assert by_id[monthly["entity_id"]]["monthly_value_chf"] == "1000.00"
    assert by_id[monthly["entity_id"]]["annual_value_chf"] == "12000.00"
    assert by_id[annual["entity_id"]]["monthly_value_chf"] == "100.00"
    assert by_id[annual["entity_id"]]["monthly_average_chf"] == "100.00"
    assert by_id[annual["entity_id"]]["annual_value_chf"] == "1200.00"
    income = get_income_status(conn, year="2026")
    assert Decimal(income["planned_income_year"]) == Decimal("13200.00")
    assert Decimal(income["actual_income_current_month"]) == Decimal("1000.00")
    assert income["status_label"] in {"Im Plan", "Unter Plan", "Über Plan", "Noch keine Ist-Daten"}
    overview = get_budget_overview(conn, "2026-05")
    assert Decimal(overview["planned_income_chf"]) > Decimal("0")
    assert Decimal(overview["actual_income_chf"]) > Decimal("0")
    archived = archive_budget_plan_item(conn, monthly["entity_id"])
    assert archived["status"] == "archived"
    assert all(p["plan_item_id"] != monthly["entity_id"] for p in list_income_plan_items(conn, year="2026", include_archived=False))


def test_income_templates_are_seeded_without_amounts_and_later_editable_with_audit() -> None:
    conn = db()
    _account, _food, _grocery, _health, salary, bonus, _unused = base(conn)
    preview = preview_income_plan_templates(conn, year="2026")
    assert preview["requires_explicit_confirm"] is True
    assert preview["template_count"] >= 10
    assert all("amount" not in str(item).lower() for item in preview["templates"])

    confirmed = confirm_income_plan_templates(conn, year="2026")
    assert confirmed["created_count"] >= 10
    plans = list_income_plan_items(conn, year="2026")
    by_name = {p["name"]: p for p in plans}
    assert "Lohn Marcel (ERNE)" in by_name
    assert "13. Monatslohn Melanie – Gasser Bauunternehmen" in by_name
    assert "Rückvergütung Solarstrom" in by_name
    assert by_name["Lohn Marcel (ERNE)"]["monthly_value_chf"] == "0.00"
    assert by_name["Lohn Marcel (ERNE)"]["annual_value_chf"] == "0.00"
    assert by_name["Lohn Marcel (ERNE)"]["employer_source"] == "ERNE"
    assert by_name["Bonus Marcel"]["target_month"] == "April"
    assert by_name["13. Monatslohn Marcel"]["target_month"] == "Dezember"

    second = confirm_income_plan_templates(conn, year="2026")
    assert second["created_count"] == 0
    assert sum(1 for p in list_income_plan_items(conn, year="2026") if p["name"] == "Lohn Marcel (ERNE)") == 1

    plan_id = by_name["Lohn Marcel (ERNE)"]["plan_item_id"]
    update_preview = preview_update_income_plan_item(conn, plan_id, {"monthly_amount_chf": "1000.00", "notes": "person=Marcel;income_type=Lohn"})
    assert update_preview["requires_explicit_confirm"] is True
    assert update_preview["review"]["monthly_value_chf"] == "1000.00"
    assert update_preview["review"]["annual_value_chf"] == "12000.00"
    updated = confirm_update_income_plan_item(conn, plan_id, update_preview["payload"])
    assert updated["status"] == "updated"
    edited = {p["plan_item_id"]: p for p in list_income_plan_items(conn, year="2026")}[plan_id]
    assert edited["monthly_value_chf"] == "1000.00"
    assert edited["annual_value_chf"] == "12000.00"
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE entity_id=?", (plan_id,)).fetchone()[0] >= 2


def test_income_template_seed_renames_legacy_lohn_marcel_to_erne_without_duplicate() -> None:
    conn = db()
    _account, _food, _grocery, _health, salary, _bonus, _unused = base(conn)
    legacy = confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": salary, "name": "Lohn Marcel", "monthly_amount_chf": "1000.00", "cadence": "monthly", "notes": "person=Marcel;income_type=Lohn;template=true"})
    result = confirm_income_plan_templates(conn, year="2026")
    assert result["renamed_count"] == 1
    plans = list_income_plan_items(conn, year="2026")
    assert sum(1 for p in plans if p["name"] == "Lohn Marcel (ERNE)") == 1
    assert all(p["name"] != "Lohn Marcel" for p in plans)
    renamed = next(p for p in plans if p["plan_item_id"] == legacy["entity_id"])
    assert renamed["name"] == "Lohn Marcel (ERNE)"
    assert renamed["monthly_value_chf"] == "1000.00"
    assert renamed["employer_source"] == "ERNE"


def test_income_annual_plan_preview_sets_monthly_average_and_due_month() -> None:
    conn = db()
    _account, _food, _grocery, _health, _salary, bonus, _unused = base(conn)
    plan = confirm_budget_plan_item(conn, {"plan_month": "2026-01", "category_id": bonus, "name": "Bonus Marcel", "annual_amount_chf": "1200.00", "cadence": "annual", "notes": "person=Marcel;income_type=Bonus;target_month=April"})
    preview = preview_update_income_plan_item(conn, plan["entity_id"], {"annual_amount_chf": "2400.00", "monthly_amount_chf": None, "cadence": "annual", "target_month": "April"})
    assert preview["review"]["annual_value_chf"] == "2400.00"
    assert preview["review"]["monthly_average_chf"] == "200.00"
    assert preview["review"]["target_month"] == "April"
