from __future__ import annotations

from fastapi import APIRouter, Depends, HTTPException
from sqlite3 import Connection

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.services.budget_accounts import archive_budget_account, confirm_create_budget_account, create_budget_account_preview, list_budget_accounts
from jarvis_finance.services.budget_categories import (
    archive_category,
    archive_tag,
    confirm_budget_category_cleanup,
    confirm_category_merge,
    confirm_create_category,
    confirm_create_tag,
    create_category_preview,
    create_tag_preview,
    delete_empty_category,
    list_category_tree_grouped,
    list_categories,
    list_tags,
    preview_budget_category_cleanup,
    preview_category_merges,
    reorder_categories,
    update_category,
)
from jarvis_finance.services.budget_csv_imports import import_budget_csv_text, import_profile_specs
from jarvis_finance.services.budget_import_status import build_import_status_audit, build_review_backlog_dashboard, mark_2025_candidates_as_reference
from jarvis_finance.services.budget_import_upload import (
    confirm_uploaded_budget_import_candidates,
    get_budget_import_session,
    list_budget_import_sessions,
    preview_uploaded_budget_import,
)
from jarvis_finance.services.budget_imports import (
    archive_reference_year_candidates,
    apply_budget_rules_to_open_candidates,
    apply_merchant_to_open_candidates,
    batch_confirm_safe_candidates,
    batch_mark_candidates,
    confirm_migros_receipts_2026,
    bulk_update_transaction_candidate_category,
    confirm_candidate_split,
    confirm_duplicate_override,
    confirm_transaction_candidate,
    create_budget_rule,
    create_merchant,
    create_merchant_alias,
    create_candidate_split,
    create_review_category_for_candidate,
    classify_swisslos_internal_transfers,
    deactivate_budget_rule,
    detect_duplicate_candidates,
    ignore_transaction_candidate,
    list_budget_rules,
    list_merchants,
    list_transaction_candidates,
    merge_merchants,
    mark_candidate_transfer,
    mark_candidate_type,
    preview_migros_receipts_2026,
    preview_batch_confirm_candidates,
    preview_safe_auto_candidates,
    preview_transaction_candidate,
    prioritize_budget_rule,
    reclassify_bank_income_candidates,
    reopen_transaction_candidate,
    review_candidate_summary,
    seed_bank_transfer_candidates_from_rows,
    seed_credit_card_candidates_from_rows,
    seed_migros_candidates_from_rows,
    seed_transaction_candidates_from_csv_text,
    seed_transaction_candidates_from_rows,
    test_budget_rule,
    update_budget_rule,
    update_merchant_category,
    update_transaction_candidate_category,
)
from jarvis_finance.services.budget_analytics import get_monthly_comparison
from jarvis_finance.services.budget_analytics_v2 import get_budget_dashboard_cockpit, get_budget_vs_actual_analysis
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_overview import get_budget_charts_2026, get_budget_overview, get_budget_status_by_category, get_category_month_detail, get_category_summary, get_income_status, get_monthly_summary
from jarvis_finance.services.budget_planning import (
    confirm_budget_planning_batch,
    confirm_category_budget_values,
    get_budget_planning_audit_timeline,
    get_budget_planning_matrix,
    preview_budget_planning_batch,
    preview_budget_planning_excel_template,
    preview_category_budget_values,
)
from jarvis_finance.services.budget_recurring import (
    archive_recurring_payment,
    confirm_manual_recurring_payment,
    confirm_recurring_candidate_action,
    detect_recurring_payment_candidates,
    get_recurring_dashboard,
    preview_manual_recurring_payment,
    preview_recurring_candidate_action,
)
from jarvis_finance.services.budget_monthly_import import (
    apply_rule_suggestion,
    get_import_history,
    get_monthly_import_dashboard,
    list_rule_suggestions,
    preview_drive_monthly_import,
    reject_rule_suggestion,
    run_monthly_import_dry_run,
    scan_budget_drive_files,
    scan_google_drive_budget_folder,
)
from jarvis_finance.services.grocery_optimizer import (
    accept_grocery_product_match,
    add_demo_migros_receipt,
    build_rappn_link_out,
    confirm_manual_grocery_mapping,
    create_manual_grocery_mapping,
    generate_grocery_optimization_report,
    get_grocery_optimizer_dashboard,
    get_grocery_provider_strategy,
    preview_manual_grocery_mapping,
    refresh_grocery_mapping_price,
    reject_grocery_product_match,
    run_grocery_optimization,
    upsert_product_match,
)
from jarvis_finance.services.grocery_price_providers import provider_for_retailer, search_and_store_product_matches, search_open_prices_spike
from jarvis_finance.services.budget_plans import (
    archive_budget_plan_item,
    confirm_budget_plan_item,
    confirm_income_plan_templates,
    confirm_update_income_plan_item,
    create_budget_plan_item_preview,
    list_budget_plan_items,
    list_income_plan_items,
    list_income_seed_candidates,
    preview_income_plan_templates,
    preview_update_income_plan_item,
    reorder_income_plan_items,
)
from jarvis_finance.services.budget_seed_review import (
    confirm_budget_plan_takeover,
    confirm_clear_category_takeover,
    confirm_seed_candidate,
    convert_unclean_range,
    ignore_seed_candidate,
    list_seed_candidates,
    preview_budget_plan_takeover,
    preview_clear_category_takeover,
    preview_seed_candidate,
    reopen_seed_candidate,
    seed_budget_candidates_from_rows,
    set_seed_candidate_status,
)
from jarvis_finance.services.budget_transactions import (
    confirm_budget_transaction,
    confirm_reverse_budget_transaction,
    confirm_transfer,
    create_budget_transaction_preview,
    create_transfer_preview,
    list_budget_transactions,
    list_budget_transfers,
    reverse_budget_transaction_preview,
    update_budget_transaction,
)
from jarvis_finance.services.transfer_pairing import (
    confirm_transfer_pair,
    list_transfer_pairs,
    propose_transfer_pairs,
    reject_transfer_pair,
)

router = APIRouter(prefix="/budget", tags=["budget"])


@router.get("/import-status-audit")
def budget_import_status_audit(conn: Connection = Depends(get_db)):
    return build_import_status_audit(conn)


@router.get("/review-backlog")
def budget_review_backlog(conn: Connection = Depends(get_db)):
    return build_review_backlog_dashboard(conn)


@router.post("/review-backlog/mark-2025-reference")
def budget_review_backlog_mark_2025_reference(conn: Connection = Depends(get_db)):
    return mark_2025_candidates_as_reference(conn)


@router.get("/grocery-optimizer/dashboard")
def grocery_optimizer_dashboard(conn: Connection = Depends(get_db)):
    return get_grocery_optimizer_dashboard(conn)


@router.get("/grocery-optimizer/provider-strategy")
def grocery_optimizer_provider_strategy():
    return get_grocery_provider_strategy()


@router.get("/grocery-optimizer/rappn-link")
def grocery_optimizer_rappn_link(product_name: str):
    return build_rappn_link_out(product_name)


@router.post("/grocery-optimizer/open-prices-spike")
def grocery_optimizer_open_prices_spike(payload: dict):
    return search_open_prices_spike(query=payload.get("query"), barcode=payload.get("barcode"), size=int(payload.get("size") or 5))


@router.post("/grocery-optimizer/demo-receipt")
def grocery_optimizer_demo_receipt(payload: dict, conn: Connection = Depends(get_db)):
    return add_demo_migros_receipt(conn, purchase_date=payload.get("purchase_date") or "2026-05-19", store_name=payload.get("store_name") or "Migros", items=payload.get("items") or [])


@router.post("/grocery-optimizer/matches")
def grocery_optimizer_match(payload: dict, conn: Connection = Depends(get_db)):
    return upsert_product_match(conn, payload["product_item_id"], retailer=payload["retailer"], candidate_product_name=payload["candidate_product_name"], candidate_url=payload.get("candidate_url") or "https://example.invalid/review", candidate_price_text=payload["candidate_price_text"], candidate_unit_price_text=payload.get("candidate_unit_price_text"), quality_flags=payload.get("quality_flags") or ["needs_review"], match_confidence=payload.get("match_confidence") or "0.60", status=payload.get("status") or "suggested", source=payload.get("source") or "manual")


@router.post("/grocery-optimizer/matches/{match_id}/accept")
def grocery_optimizer_accept_match(match_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    payload = payload or {}
    return accept_grocery_product_match(conn, match_id, user_note=payload.get("user_note"), save_as_preferred=bool(payload.get("save_as_preferred", True)))


@router.post("/grocery-optimizer/matches/{match_id}/reject")
def grocery_optimizer_reject_match(match_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reject_grocery_product_match(conn, match_id, user_note=(payload or {}).get("user_note"))


@router.post("/grocery-optimizer/manual-mapping/preview")
def grocery_optimizer_manual_mapping_preview(payload: dict, conn: Connection = Depends(get_db)):
    return preview_manual_grocery_mapping(conn, product_item_id=payload["product_item_id"], target_retailer=payload["target_retailer"], target_product_name=payload["target_product_name"], target_product_url=payload["target_product_url"], candidate_price_text=payload.get("candidate_price_text"), candidate_unit_price_text=payload.get("candidate_unit_price_text"), target_package_size=payload.get("target_package_size"), target_unit=payload.get("target_unit"), target_price_date=payload.get("target_price_date"), target_price_source=payload.get("target_price_source"), user_note=payload.get("user_note"))


@router.post("/grocery-optimizer/manual-mapping/confirm")
def grocery_optimizer_manual_mapping_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_manual_grocery_mapping(conn, payload)


@router.post("/grocery-optimizer/manual-mapping")
def grocery_optimizer_manual_mapping(payload: dict, conn: Connection = Depends(get_db)):
    return create_manual_grocery_mapping(conn, product_item_id=payload["product_item_id"], target_retailer=payload["target_retailer"], target_product_name=payload["target_product_name"], target_product_url=payload["target_product_url"], target_brand=payload.get("target_brand"), target_package_size=payload.get("target_package_size"), target_unit=payload.get("target_unit"), target_price_date=payload.get("target_price_date"), target_price_source=payload.get("target_price_source"), match_type=payload.get("match_type") or "manual_match", candidate_price_text=payload.get("candidate_price_text"), candidate_unit_price_text=payload.get("candidate_unit_price_text"), user_note=payload.get("user_note"))


@router.post("/grocery-optimizer/mappings/{mapping_id}/refresh-price")
def grocery_optimizer_refresh_mapping_price(mapping_id: str, conn: Connection = Depends(get_db)):
    return refresh_grocery_mapping_price(conn, mapping_id)


@router.post("/grocery-optimizer/optimize")
def grocery_optimizer_run(payload: dict, conn: Connection = Depends(get_db)):
    return run_grocery_optimization(conn, payload["receipt_id"], selected_retailers=payload.get("selected_retailers") or ["Coop", "Aldi Suisse", "Lidl", "Denner", "Otto's"], max_store_count=int(payload.get("max_store_count") or 3), included_product_item_ids=payload.get("included_product_item_ids"))


@router.post("/grocery-optimizer/search-prices")
def grocery_optimizer_search_prices(payload: dict, conn: Connection = Depends(get_db)):
    retailers = (payload.get("retailers") or ["Coop"])[: int(payload.get("max_retailers") or 3)]
    providers = [provider_for_retailer(retailer) for retailer in retailers]
    return search_and_store_product_matches(
        conn,
        payload["receipt_id"],
        providers=providers,
        included_product_item_ids=payload.get("included_product_item_ids"),
        max_products=int(payload.get("max_products") or 5),
        use_cache=bool(payload.get("use_cache", True)),
        only_safe_matches=bool(payload.get("only_safe_matches", True)),
        prefer_known_mappings=bool(payload.get("prefer_known_mappings", True)),
    )


@router.post("/grocery-optimizer/report")
def grocery_optimizer_report(payload: dict, conn: Connection = Depends(get_db)):
    return generate_grocery_optimization_report(conn, payload["run_id"])


@router.get("/monthly-import/dashboard")
def monthly_import_dashboard(budget_year: str = "2026", conn: Connection = Depends(get_db)):
    return get_monthly_import_dashboard(conn, budget_year=budget_year)


@router.get("/monthly-import/history")
def monthly_import_history(limit: int = 30, conn: Connection = Depends(get_db)):
    return get_import_history(conn, limit=limit)


@router.post("/monthly-import/drive-scan")
def monthly_import_drive_scan(payload: dict | None = None, conn: Connection = Depends(get_db)):
    files = (payload or {}).get("files")
    return scan_budget_drive_files(files) if files is not None else scan_google_drive_budget_folder()


@router.post("/monthly-import/dry-run")
def monthly_import_dry_run(payload: dict, conn: Connection = Depends(get_db)):
    return run_monthly_import_dry_run(conn, payload.get("files") or [], default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.post("/monthly-import/confirm-candidates")
def monthly_import_confirm_candidates(payload: dict, conn: Connection = Depends(get_db)):
    return preview_drive_monthly_import(conn, payload.get("files") or [], dry_run=False, default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.get("/rules/suggestions")
def budget_rule_suggestions(status: str | None = None, conn: Connection = Depends(get_db)):
    return list_rule_suggestions(conn, status=status)


@router.post("/rules/suggestions/{suggestion_id}/apply")
def budget_rule_suggestion_apply(suggestion_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return apply_rule_suggestion(conn, suggestion_id, apply_to_open=bool((payload or {}).get("apply_to_open")))


@router.post("/rules/suggestions/{suggestion_id}/reject")
def budget_rule_suggestion_reject(suggestion_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reject_rule_suggestion(conn, suggestion_id, later=bool((payload or {}).get("later")))


@router.get("/planning/matrix")
def budget_planning_matrix(year: str = "2026", current_month: str | None = None, conn: Connection = Depends(get_db)):
    return get_budget_planning_matrix(conn, year=year, current_month=current_month)


@router.post("/planning/categories/{category_id}/preview")
def budget_planning_category_preview(category_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return preview_category_budget_values(conn, category_id, payload)


@router.post("/planning/categories/{category_id}/confirm")
def budget_planning_category_confirm(category_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_category_budget_values(conn, category_id, payload)


@router.get("/planning/categories/{category_id}/audit")
def budget_planning_category_audit(category_id: str, limit: int = 25, conn: Connection = Depends(get_db)):
    return get_budget_planning_audit_timeline(conn, category_id, limit=limit)


@router.post("/planning/batch/preview")
def budget_planning_batch_preview(payload: dict, conn: Connection = Depends(get_db)):
    return preview_budget_planning_batch(conn, payload)


@router.post("/planning/batch/confirm")
def budget_planning_batch_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_budget_planning_batch(conn, payload)


@router.post("/planning/excel-template/preview")
def budget_planning_excel_template_preview(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return preview_budget_planning_excel_template(conn, payload or {})


@router.get("/recurring")
def recurring_dashboard(today: str | None = None, conn: Connection = Depends(get_db)):
    return get_recurring_dashboard(conn, today=today)


@router.post("/recurring/detect")
def recurring_detect(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return detect_recurring_payment_candidates(conn, today=(payload or {}).get("today"))


@router.post("/recurring/manual/preview")
def recurring_manual_preview(payload: dict, conn: Connection = Depends(get_db)):
    return preview_manual_recurring_payment(conn, payload)


@router.post("/recurring/manual/confirm")
def recurring_manual_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_manual_recurring_payment(conn, payload)


@router.post("/recurring/candidates/{candidate_id}/preview")
def recurring_candidate_preview(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return preview_recurring_candidate_action(conn, candidate_id, payload)


@router.post("/recurring/candidates/{candidate_id}/confirm")
def recurring_candidate_confirm(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    body = dict(payload)
    body.setdefault("candidate_id", candidate_id)
    return confirm_recurring_candidate_action(conn, body)


@router.post("/recurring/{recurring_id}/archive")
def recurring_archive(recurring_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return archive_recurring_payment(conn, recurring_id, payload or {})


@router.get("/dashboard/cockpit")
def dashboard_cockpit(month: str = "2026-05", conn: Connection = Depends(get_db)):
    return get_budget_dashboard_cockpit(conn, month=month)


@router.get("/analysis/budget-vs-actual")
def budget_vs_actual_analysis(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_budget_vs_actual_analysis(conn, year=year)


@router.get("/overview")
def overview(month: str | None = None, conn: Connection = Depends(get_db)):
    return get_budget_overview(conn, month)


@router.get("/status/categories")
def category_budget_status(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_budget_status_by_category(conn, year=year)


@router.get("/status/categories/{category_id}/months/{month}")
def category_month_detail(category_id: str, month: str, conn: Connection = Depends(get_db)):
    return get_category_month_detail(conn, category_id=category_id, month=month)


@router.get("/charts")
def budget_charts(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_budget_charts_2026(conn, year=year)


@router.get("/analysis/categories")
def category_analysis(year: str = "2026", month: str | None = None, tx_type: str = "expense", conn: Connection = Depends(get_db)):
    return get_category_analysis_v2(conn, year=year, month=month, tx_type=tx_type)


@router.get("/analysis/data-explorer")
def data_explorer(
    year: str = "2026",
    month: str = "",
    date_from: str = "",
    date_to: str = "",
    type: str = "both",
    category_id: str = "",
    merchant: str = "",
    account_id: str = "",
    source_type: str = "",
    tag: str = "",
    amount_min: str = "",
    amount_max: str = "",
    search: str = "",
    recurring_type: str = "",
    conn: Connection = Depends(get_db),
):
    return get_budget_data_explorer(conn, year=year, month=month, date_from=date_from, date_to=date_to, type=type, category_id=category_id, merchant=merchant, account_id=account_id, source_type=source_type, tag=tag, amount_min=amount_min, amount_max=amount_max, search=search, recurring_type=recurring_type)


@router.get("/analysis/merchants")
def merchant_analysis(year: str = "2026", month: str = "", merchant: str = "", type: str = "expense", conn: Connection = Depends(get_db)):
    return get_merchant_analysis(conn, year=year, month=month, merchant=merchant, type=type)


@router.get("/analysis/budget-deviations")
def budget_deviation_analysis(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_budget_deviation_analysis(conn, year=year)


@router.get("/analysis/monthly")
def monthly_analysis(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_monthly_comparison(conn, year=year)


@router.get("/analysis/monthly-comparison")
def monthly_comparison(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_monthly_comparison(conn, year=year)


@router.get("/income/status")
def income_budget_status(year: str = "2026", conn: Connection = Depends(get_db)):
    return get_income_status(conn, year=year)


@router.get("/income/plans")
def income_plans(year: str = "2026", include_archived: bool = False, conn: Connection = Depends(get_db)):
    return list_income_plan_items(conn, year=year, include_archived=include_archived)


@router.get("/income/seed-candidates")
def income_seed_candidates(year: str = "2026", conn: Connection = Depends(get_db)):
    return list_income_seed_candidates(conn, year=year)


@router.post("/income/plans/preview")
def income_plans_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_budget_plan_item_preview(conn, payload)


@router.post("/income/plans/confirm")
def income_plans_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_budget_plan_item(conn, payload)


@router.post("/income/templates/preview")
def income_templates_preview(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return preview_income_plan_templates(conn, year=str((payload or {}).get("year") or "2026"))


@router.post("/income/templates/confirm")
def income_templates_confirm(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return confirm_income_plan_templates(conn, year=str((payload or {}).get("year") or "2026"))


@router.post("/income/plans/{plan_item_id}/update/preview")
def income_plans_update_preview(plan_item_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return preview_update_income_plan_item(conn, plan_item_id, payload)


@router.post("/income/plans/{plan_item_id}/update/confirm")
def income_plans_update_confirm(plan_item_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_update_income_plan_item(conn, plan_item_id, payload)


@router.post("/income/plans/{plan_item_id}/archive")
def income_plans_archive(plan_item_id: str, conn: Connection = Depends(get_db)):
    return archive_budget_plan_item(conn, plan_item_id)


@router.post("/income/plans/reorder")
def income_plans_reorder(payload: dict, conn: Connection = Depends(get_db)):
    return reorder_income_plan_items(conn, payload.get("plan_item_ids") or [], year=str(payload.get("year") or "2026"))


@router.get("/monthly-summary")
def monthly_summary(month: str | None = None, conn: Connection = Depends(get_db)):
    return get_monthly_summary(conn, month)


@router.get("/categories/summary")
def categories_summary(month: str | None = None, conn: Connection = Depends(get_db)):
    return get_category_summary(conn, month)


@router.get("/accounts")
def accounts(include_archived: bool = False, conn: Connection = Depends(get_db)):
    return list_budget_accounts(conn, include_archived=include_archived)


@router.post("/accounts/preview")
def accounts_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_budget_account_preview(conn, payload)


@router.post("/accounts/confirm")
def accounts_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_create_budget_account(conn, payload)


@router.post("/accounts/{account_id}/archive")
def accounts_archive(account_id: str, conn: Connection = Depends(get_db)):
    return archive_budget_account(conn, account_id)


@router.get("/categories")
def categories(include_archived: bool = False, grouped: bool = False, conn: Connection = Depends(get_db)):
    if grouped:
        return list_category_tree_grouped(conn, include_archived=include_archived)
    return list_categories(conn, include_archived=include_archived)


@router.get("/categories/tree")
def category_tree(include_archived: bool = False, conn: Connection = Depends(get_db)):
    return list_category_tree_grouped(conn, include_archived=include_archived)


@router.get("/tags")
def tags(include_archived: bool = False, conn: Connection = Depends(get_db)):
    return list_tags(conn, include_archived=include_archived)


@router.post("/tags/preview")
def tags_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_tag_preview(conn, payload)


@router.post("/tags/confirm")
def tags_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_create_tag(conn, payload)


@router.post("/tags/{tag_id}/archive")
def tags_archive(tag_id: str, conn: Connection = Depends(get_db)):
    return archive_tag(conn, tag_id)


@router.get("/plans")
def plans(month: str | None = None, include_archived: bool = True, conn: Connection = Depends(get_db)):
    return list_budget_plan_items(conn, month=month, include_archived=include_archived)


@router.post("/plans/preview")
def plans_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_budget_plan_item_preview(conn, payload)


@router.post("/plans/confirm")
def plans_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_budget_plan_item(conn, payload)


@router.get("/seed-candidates")
def seed_candidates(status: str | None = None, candidate_type: str | None = None, conn: Connection = Depends(get_db)):
    return list_seed_candidates(conn, status=status, candidate_type=candidate_type)


@router.get("/import-profiles")
def budget_import_profiles():
    return import_profile_specs()


@router.post("/import-profiles/{profile_name}/dry-run")
def budget_import_profile_dry_run(profile_name: str, payload: dict, conn: Connection = Depends(get_db)):
    return import_budget_csv_text(conn, profile_name, str(payload.get("csv_text") or ""), source_file_label=payload.get("source_file_label") or f"{profile_name}.csv", dry_run=True, default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.post("/import-profiles/{profile_name}/candidates")
def budget_import_profile_candidates(profile_name: str, payload: dict, conn: Connection = Depends(get_db)):
    return import_budget_csv_text(conn, profile_name, str(payload.get("csv_text") or ""), source_file_label=payload.get("source_file_label") or f"{profile_name}.csv", dry_run=False, default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.post("/import/upload/preview")
def budget_import_upload_preview(payload: dict, conn: Connection = Depends(get_db)):
    return preview_uploaded_budget_import(conn, filename=str(payload.get("filename") or "budget.csv"), csv_text=str(payload.get("csv_text") or ""), profile=payload.get("profile") or "auto", default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.post("/import/upload/confirm-candidates")
def budget_import_upload_confirm_candidates(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_uploaded_budget_import_candidates(conn, str(payload.get("preview_id") or payload.get("import_session_id") or ""), default_category_id=payload.get("default_category_id"), income_category_id=payload.get("income_category_id"))


@router.get("/import/sessions")
def budget_import_sessions(limit: int = 30, conn: Connection = Depends(get_db)):
    return list_budget_import_sessions(conn, limit=limit)


@router.get("/import/sessions/{import_session_id}")
def budget_import_session(import_session_id: str, conn: Connection = Depends(get_db)):
    return get_budget_import_session(conn, import_session_id)


@router.get("/transaction-candidates")
def transaction_candidates(status: str | None = None, tab: str | None = None, source_type: str | None = None, import_session_id: str | None = None, category_id: str | None = None, merchant: str | None = None, rule: str | None = None, min_confidence: str | None = None, date_from: str | None = None, date_to: str | None = None, amount_min: str | None = None, amount_max: str | None = None, sort_by: str = "created_at", sort_dir: str = "desc", budget_year: str = "2026", include_reference: bool = False, conn: Connection = Depends(get_db)):
    return list_transaction_candidates(conn, status=status, tab=tab, source_type=source_type, import_session_id=import_session_id, category_id=category_id, merchant=merchant, rule=rule, min_confidence=min_confidence, date_from=date_from, date_to=date_to, amount_min=amount_min, amount_max=amount_max, sort_by=sort_by, sort_dir=sort_dir, budget_year=budget_year, include_reference=include_reference)


@router.get("/transaction-candidates/summary")
def transaction_candidates_summary(budget_year: str = "2026", conn: Connection = Depends(get_db)):
    return review_candidate_summary(conn, budget_year=budget_year)


@router.post("/transaction-candidates/generate")
def transaction_candidates_generate(payload: dict, conn: Connection = Depends(get_db)):
    source_type = payload.get("source_type") or "csv_seed"
    rows = payload.get("rows") or []
    if source_type == "credit_card_csv":
        return seed_credit_card_candidates_from_rows(conn, rows, source_file_label=payload.get("source_file_label") or "Credit card CSV", subscription_category_id=payload.get("subscription_category_id"), migros_covered=payload.get("migros_covered", True))
    if source_type == "migros_csv":
        return seed_migros_candidates_from_rows(conn, rows, source_file_label=payload.get("source_file_label") or "Migros CSV", auto_category_id=payload.get("auto_category_id"), threshold_chf=str(payload.get("threshold_chf") or "50.00"))
    if source_type == "bank_csv":
        return seed_bank_transfer_candidates_from_rows(conn, rows, source_file_label=payload.get("source_file_label") or "Bank CSV", income_category_id=payload.get("income_category_id"))
    if payload.get("csv_text"):
        return seed_transaction_candidates_from_csv_text(conn, payload["csv_text"], source_file_label=payload.get("source_file_label") or "CSV Import", source_type=source_type)
    return seed_transaction_candidates_from_rows(conn, rows, source_file_label=payload.get("source_file_label") or "Budget Import", source_type=source_type)


@router.post("/transaction-candidates/bank-income/reclassify")
def transaction_candidates_bank_income_reclassify(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reclassify_bank_income_candidates(conn, budget_year=str((payload or {}).get("budget_year") or "2026"))


@router.post("/transaction-candidates/internal-transfers/swisslos/reclassify")
def transaction_candidates_swisslos_transfer_reclassify(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return classify_swisslos_internal_transfers(conn, budget_year=str((payload or {}).get("budget_year") or "2026"))


@router.get("/transfer-pairs")
def transfer_pairs(status: str | None = None, conn: Connection = Depends(get_db)):
    return list_transfer_pairs(conn, status=status)


@router.post("/transfer-pairs/match")
def transfer_pairs_match(payload: dict | None = None, conn: Connection = Depends(get_db)):
    window = int((payload or {}).get("date_window_days") or 3)
    return propose_transfer_pairs(conn, date_window_days=window)


@router.post("/transfer-pairs/{pair_id}/confirm")
def transfer_pairs_confirm(pair_id: str, payload: dict, conn: Connection = Depends(get_db)):
    if payload.get("confirm") is not True:
        raise HTTPException(status_code=422, detail="explicit confirm=true required")
    return confirm_transfer_pair(
        conn,
        pair_id,
        decision_by="user",
        note=str(payload.get("note") or "") or None,
    )


@router.post("/transfer-pairs/{pair_id}/reject")
def transfer_pairs_reject(pair_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reject_transfer_pair(
        conn,
        pair_id,
        decision_by="user",
        note=str((payload or {}).get("note") or "") or None,
    )


@router.post("/transaction-candidates/migros-2026/preview")
def transaction_candidates_migros_2026_preview(payload: dict | None = None, conn: Connection = Depends(get_db)):
    return preview_migros_receipts_2026(conn, year=str((payload or {}).get("year") or "2026"))


@router.post("/transaction-candidates/migros-2026/confirm")
def transaction_candidates_migros_2026_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_migros_receipts_2026(conn, account_id=str(payload.get("account_id") or ""), year=str(payload.get("year") or "2026"))


@router.post("/transaction-candidates/{candidate_id}/preview")
def transaction_candidates_preview(candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return preview_transaction_candidate(conn, candidate_id, payload or {})


@router.post("/transaction-candidates/{candidate_id}/confirm")
def transaction_candidates_confirm(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_transaction_candidate(conn, candidate_id, payload)


@router.post("/transaction-candidates/{candidate_id}/confirm-duplicate-override")
def transaction_candidates_confirm_duplicate_override(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_duplicate_override(conn, candidate_id, payload)


@router.post("/transaction-candidates/{candidate_id}/ignore")
def transaction_candidates_ignore(candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return ignore_transaction_candidate(conn, candidate_id, (payload or {}).get("note"))


@router.post("/transaction-candidates/{candidate_id}/reopen")
def transaction_candidates_reopen(candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reopen_transaction_candidate(conn, candidate_id, (payload or {}).get("note"))


@router.post("/transaction-candidates/{candidate_id}/category")
def transaction_candidates_category(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return update_transaction_candidate_category(conn, candidate_id, payload.get("category_id"), payload.get("note"))


@router.post("/transaction-candidates/bulk-category")
def transaction_candidates_bulk_category(payload: dict, conn: Connection = Depends(get_db)):
    category_id = str(payload.get("category_id") or "")
    return bulk_update_transaction_candidate_category(conn, candidate_ids=payload.get("candidate_ids") or [], category_id=category_id, note=payload.get("note"))


@router.post("/transaction-candidates/{candidate_id}/split")
def transaction_candidates_split(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return create_candidate_split(conn, candidate_id, payload.get("splits") or [])


@router.post("/transaction-candidates/{candidate_id}/split/confirm")
def transaction_candidates_split_confirm(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_candidate_split(conn, candidate_id, payload)


@router.post("/transaction-candidates/{candidate_id}/category/create")
def transaction_candidates_create_category(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return create_review_category_for_candidate(conn, candidate_id, payload)


@router.post("/transaction-candidates/{candidate_id}/transfer")
def transaction_candidates_transfer(candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return mark_candidate_transfer(conn, candidate_id, (payload or {}).get("note"))


@router.post("/transaction-candidates/{candidate_id}/type")
def transaction_candidates_type(candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return mark_candidate_type(conn, candidate_id, str(payload.get("candidate_type") or ""), (payload or {}).get("note"))


@router.post("/transaction-candidates/batch-confirm-safe/preview")
def transaction_candidates_batch_confirm_safe_preview(conn: Connection = Depends(get_db)):
    return preview_safe_auto_candidates(conn)


@router.post("/transaction-candidates/batch/preview")
def transaction_candidates_batch_preview(payload: dict, conn: Connection = Depends(get_db)):
    return preview_batch_confirm_candidates(conn, candidate_ids=payload.get("candidate_ids") or [], account_id=str(payload.get("account_id") or ""), budget_year=str(payload.get("budget_year") or "2026"))


@router.post("/transaction-candidates/batch")
def transaction_candidates_batch(payload: dict, conn: Connection = Depends(get_db)):
    return batch_mark_candidates(conn, candidate_ids=payload.get("candidate_ids") or [], action=str(payload.get("action") or ""), account_id=payload.get("account_id"), category_id=payload.get("category_id"), preview_id=payload.get("preview_id"), note=payload.get("note"), budget_year=str(payload.get("budget_year") or "2026"))


@router.post("/transaction-candidates/archive-reference-year")
def transaction_candidates_archive_reference(payload: dict | None = None, conn: Connection = Depends(get_db)):
    payload = payload or {}
    return archive_reference_year_candidates(conn, active_year=str(payload.get("active_year") or "2026"), reference_year=str(payload.get("reference_year") or "2025"))


@router.post("/transaction-candidates/batch-confirm-safe")
def transaction_candidates_batch_confirm_safe(payload: dict, conn: Connection = Depends(get_db)):
    account_id = str(payload.get("account_id") or "")
    return batch_confirm_safe_candidates(conn, account_id=account_id, candidate_ids=payload.get("candidate_ids") or None)


@router.post("/transaction-candidates/detect-duplicates")
def transaction_candidates_detect_duplicates(conn: Connection = Depends(get_db)):
    return detect_duplicate_candidates(conn)


@router.get("/merchants")
def merchants(include_inactive: bool = False, conn: Connection = Depends(get_db)):
    return list_merchants(conn, include_inactive=include_inactive)


@router.post("/merchants")
def merchants_create(payload: dict, conn: Connection = Depends(get_db)):
    return create_merchant(conn, payload)


@router.post("/merchants/{merchant_id}/aliases")
def merchants_create_alias(merchant_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return create_merchant_alias(conn, merchant_id, payload)


@router.post("/merchants/{merchant_id}/category")
def merchants_update_category(merchant_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return update_merchant_category(conn, merchant_id, payload.get("category_id"))


@router.post("/merchants/{merchant_id}/apply")
def merchants_apply(merchant_id: str, conn: Connection = Depends(get_db)):
    return apply_merchant_to_open_candidates(conn, merchant_id)


@router.post("/merchants/merge")
def merchants_merge(payload: dict, conn: Connection = Depends(get_db)):
    return merge_merchants(conn, str(payload.get("source_merchant_id") or ""), str(payload.get("target_merchant_id") or ""))


@router.post("/seed-candidates/generate")
def seed_candidates_generate(payload: dict, conn: Connection = Depends(get_db)):
    return seed_budget_candidates_from_rows(conn, payload.get("workbook_rows") or {}, source_file_label=payload.get("source_file_label") or "Budget workbook")


@router.post("/seed-candidates/{seed_candidate_id}/preview")
def seed_candidates_preview(seed_candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return preview_seed_candidate(conn, seed_candidate_id, payload or {})


@router.post("/seed-candidates/{seed_candidate_id}/confirm")
def seed_candidates_confirm(seed_candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_seed_candidate(conn, seed_candidate_id, payload)


@router.post("/seed-candidates/{seed_candidate_id}/ignore")
def seed_candidates_ignore(seed_candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return ignore_seed_candidate(conn, seed_candidate_id, (payload or {}).get("note"))


@router.post("/seed-candidates/{seed_candidate_id}/reopen")
def seed_candidates_reopen(seed_candidate_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reopen_seed_candidate(conn, seed_candidate_id, (payload or {}).get("note"))


@router.post("/seed-candidates/{seed_candidate_id}/status")
def seed_candidates_status(seed_candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return set_seed_candidate_status(conn, seed_candidate_id, payload.get("status"), payload.get("note"))


@router.post("/seed-candidates/{seed_candidate_id}/convert-unclean")
def seed_candidates_convert_unclean(seed_candidate_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return convert_unclean_range(conn, seed_candidate_id, payload.get("target_type"), payload)


@router.post("/seed-candidates/categories/preview-clear-takeover")
def seed_candidates_categories_preview_clear_takeover(conn: Connection = Depends(get_db)):
    return preview_clear_category_takeover(conn)


@router.post("/seed-candidates/categories/confirm-clear-takeover")
def seed_candidates_categories_confirm_clear_takeover(conn: Connection = Depends(get_db)):
    return confirm_clear_category_takeover(conn)


@router.post("/seed-candidates/budget-plans/preview-takeover")
def seed_candidates_budget_plans_preview_takeover(conn: Connection = Depends(get_db)):
    return preview_budget_plan_takeover(conn)


@router.post("/seed-candidates/budget-plans/confirm-takeover")
def seed_candidates_budget_plans_confirm_takeover(conn: Connection = Depends(get_db)):
    return confirm_budget_plan_takeover(conn)


@router.post("/categories/preview")
def categories_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_category_preview(conn, payload)


@router.post("/categories/confirm")
def categories_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_create_category(conn, payload)


@router.post("/categories/{category_id}/update")
def categories_update(category_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return update_category(conn, category_id, payload)


@router.post("/categories/{category_id}/archive")
def categories_archive(category_id: str, conn: Connection = Depends(get_db)):
    return archive_category(conn, category_id)


@router.post("/categories/{category_id}/delete-empty")
def categories_delete_empty(category_id: str, conn: Connection = Depends(get_db)):
    return delete_empty_category(conn, category_id)


@router.delete("/categories/{category_id}")
def categories_delete(category_id: str, conn: Connection = Depends(get_db)):
    return delete_empty_category(conn, category_id)


@router.post("/categories/cleanup/preview")
def categories_cleanup_preview(conn: Connection = Depends(get_db)):
    return preview_budget_category_cleanup(conn)


@router.post("/categories/cleanup/confirm")
def categories_cleanup_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_budget_category_cleanup(conn, payload)


@router.post("/categories/reorder")
def categories_reorder(payload: dict, conn: Connection = Depends(get_db)):
    return reorder_categories(conn, payload.get("category_ids") or [], category_type=str(payload.get("category_type") or "expense"))


@router.post("/categories/merge/preview")
def categories_merge_preview(conn: Connection = Depends(get_db)):
    return preview_category_merges(conn)


@router.post("/categories/merge/confirm")
def categories_merge_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_category_merge(conn, payload)


@router.get("/rules")
def rules(include_inactive: bool = False, conn: Connection = Depends(get_db)):
    return list_budget_rules(conn, include_inactive=include_inactive)


@router.post("/rules")
def rules_create(payload: dict, conn: Connection = Depends(get_db)):
    return create_budget_rule(conn, payload)


@router.post("/rules/{rule_id}/deactivate")
def rules_deactivate(rule_id: str, conn: Connection = Depends(get_db)):
    return deactivate_budget_rule(conn, rule_id)


@router.post("/rules/{rule_id}/update")
def rules_update(rule_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return update_budget_rule(conn, rule_id, payload)


@router.post("/rules/{rule_id}/priority")
def rules_prioritize(rule_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return prioritize_budget_rule(conn, rule_id, int(payload.get("priority") or 100))


@router.post("/rules/{rule_id}/test")
def rules_test(rule_id: str, conn: Connection = Depends(get_db)):
    return test_budget_rule(conn, rule_id)


@router.post("/rules/{rule_id}/apply")
def rules_apply(rule_id: str, conn: Connection = Depends(get_db)):
    return apply_budget_rules_to_open_candidates(conn, rule_id)


@router.get("/transactions")
def transactions(month: str | None = None, year: str | None = "2026", account_id: str | None = None, category_id: str | None = None, transaction_type: str | None = None, merchant: str | None = None, tag: str | None = None, status: str | None = None, source_type: str | None = None, search: str | None = None, date_from: str | None = None, date_to: str | None = None, conn: Connection = Depends(get_db)):
    return list_budget_transactions(conn, month=month, year=year, account_id=account_id, category_id=category_id, transaction_type=transaction_type, merchant=merchant, tag=tag, status=status, source_type=source_type, search=search, date_from=date_from, date_to=date_to)


@router.post("/transactions/preview")
def transactions_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_budget_transaction_preview(conn, payload)


@router.post("/transactions/confirm")
def transactions_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_budget_transaction(conn, payload)


@router.post("/transactions/{transaction_id}/update")
def transactions_update(transaction_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return update_budget_transaction(conn, transaction_id, payload)


@router.post("/transactions/{transaction_id}/reverse/preview")
def transactions_reverse_preview(transaction_id: str, payload: dict | None = None, conn: Connection = Depends(get_db)):
    return reverse_budget_transaction_preview(conn, transaction_id, payload or {})


@router.post("/transactions/{transaction_id}/reverse/confirm")
def transactions_reverse_confirm(transaction_id: str, payload: dict, conn: Connection = Depends(get_db)):
    return confirm_reverse_budget_transaction(conn, transaction_id, payload)


@router.get("/transfers")
def transfers(year: str | None = "2026", account_id: str | None = None, transfer_type: str | None = None, conn: Connection = Depends(get_db)):
    return list_budget_transfers(conn, year=year, account_id=account_id, transfer_type=transfer_type)


@router.post("/transfers/preview")
def transfers_preview(payload: dict, conn: Connection = Depends(get_db)):
    return create_transfer_preview(conn, payload)


@router.post("/transfers/confirm")
def transfers_confirm(payload: dict, conn: Connection = Depends(get_db)):
    return confirm_transfer(conn, payload)
