from __future__ import annotations

from decimal import Decimal, InvalidOperation
from sqlite3 import Connection
from typing import Any

from fastapi import HTTPException

from jarvis_finance.audit.log import record_audit_event
from jarvis_finance.budget.excel_seed import analyze_budget_workbook_rows
from jarvis_finance.services.budget_categories import confirm_create_category, create_category_preview
from jarvis_finance.services.budget_common import new_id, now, row_to_dict
from jarvis_finance.services.budget_imports import seed_transaction_candidates_from_rows
from jarvis_finance.services.budget_plans import confirm_budget_plan_item, create_budget_plan_item_preview

CANDIDATE_TYPES = {"category", "budget_plan", "recurring_candidate", "unclean_range"}
STATUSES = {"pending", "accepted", "edited", "ignored", "needs_review", "confirmed"}
PERIOD_TYPES = {"monthly", "annual", "fixed_like", "unclear"}


def _decimal_text(value: object) -> str | None:
    if value in (None, ""):
        return None
    try:
        return format(Decimal(str(value).strip().replace("'", "").replace(",", ".")), "f")
    except (InvalidOperation, ValueError):
        return None


def _category_type(label: str) -> str:
    low = label.lower()
    if any(word in low for word in ("lohn", "gehalt", "einnah", "income")):
        return "income"
    if any(word in low for word in ("sparen", "invest", "transfer")):
        return "neutral"
    return "expense"


def _candidate_row(**kwargs: Any) -> dict[str, Any]:
    ts = now()
    row = {
        "seed_candidate_id": kwargs.get("seed_candidate_id") or new_id("bseed"),
        "source_file_label": kwargs.get("source_file_label") or "Budget workbook",
        "source_sheet": kwargs.get("source_sheet") or "",
        "source_row_or_range": kwargs.get("source_row_or_range") or "",
        "candidate_type": kwargs.get("candidate_type") or "category",
        "source_label": kwargs.get("source_label") or None,
        "proposed_category_id": kwargs.get("proposed_category_id") or None,
        "proposed_parent_label": kwargs.get("proposed_parent_label") or None,
        "proposed_name": kwargs.get("proposed_name") or None,
        "proposed_period_type": kwargs.get("proposed_period_type") or None,
        "proposed_amount_text": _decimal_text(kwargs.get("proposed_amount_text")),
        "currency": kwargs.get("currency") or "CHF",
        "confidence": kwargs.get("confidence") or "0.70",
        "requires_review": bool(kwargs.get("requires_review", False)),
        "status": kwargs.get("status") or ("needs_review" if kwargs.get("requires_review") else "pending"),
        "notes": kwargs.get("notes") or None,
        "created_at": ts,
        "updated_at": ts,
    }
    if row["candidate_type"] not in CANDIDATE_TYPES:
        raise HTTPException(status_code=422, detail="invalid candidate_type")
    if row["status"] not in STATUSES:
        raise HTTPException(status_code=422, detail="invalid candidate status")
    return row


def _insert_candidate(conn: Connection, row: dict[str, Any]) -> None:
    conn.execute(
        """
        INSERT INTO budget_seed_candidates(
            seed_candidate_id, source_file_label, source_sheet, source_row_or_range,
            candidate_type, source_label, proposed_category_id, proposed_parent_label,
            proposed_name, proposed_period_type, proposed_amount_text, currency,
            confidence, requires_review, status, notes, created_at, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """,
        (
            row["seed_candidate_id"], row["source_file_label"], row["source_sheet"], row["source_row_or_range"],
            row["candidate_type"], row["source_label"], row["proposed_category_id"], row["proposed_parent_label"],
            row["proposed_name"], row["proposed_period_type"], row["proposed_amount_text"], row["currency"],
            row["confidence"], 1 if row["requires_review"] else 0, row["status"], row["notes"], row["created_at"], row["updated_at"],
        ),
    )


def _find_amount(row: list[Any], headers: list[str], prefer: str) -> str | None:
    month_headers = {"jan", "januar", "monatlich", "monthly", "feb", "februar", "mar", "mär", "maerz", "märz", "apr", "april", "mai", "jun", "juni", "jul", "juli", "aug", "august", "sep", "sept", "september", "okt", "oktober", "nov", "november", "dez", "dezember"}
    year_headers = {"jahr", "total", "summe", "jährlich", "jaehrlich", "annual"}
    header_set = year_headers if prefer == "annual" else month_headers
    for idx, header in enumerate(headers):
        if header in header_set and idx < len(row):
            amount = _decimal_text(row[idx])
            if amount is not None:
                return amount
    return None


def _normalise(value: object) -> str:
    return str(value or "").strip().lower().replace("ä", "ae").replace("ö", "oe").replace("ü", "ue")



def _income_bucket(label: str) -> str:
    low = _normalise(label)
    if 'marcel' in low and any(w in low for w in ('lohn', 'einkommen', 'einnahme', 'salary')):
        return 'Lohn Marcel'
    if 'melanie' in low and any(w in low for w in ('lohn', 'einkommen', 'einnahme', 'salary')):
        return 'Lohn Melanie'
    if any(w in low for w in ('gratifikation', 'bonus', '13.', '13 ')):
        return 'Gratifikation'
    return 'Sonstige Einnahmen'


def _income_candidates_from_2026_rows(conn: Connection, rows: list[list[Any]], source_file_label: str, counts: dict[str, int], seen_keys: set[tuple[str, str, str]]) -> None:
    # User-specified Excel area: sheet 2026, roughly rows 37-45, columns B-E.
    # Keep this as review candidates only; no productive writes here.
    for row_number, row in enumerate(rows[36:45], start=37):
        cells = list(row[1:5])
        label_parts = [str(c).strip() for c in cells if c not in (None, '') and _decimal_text(c) is None]
        label = ' '.join(label_parts).strip()
        if not label:
            continue
        low = _normalise(label)
        if not any(word in low for word in ('lohn', 'einkommen', 'einnah', 'gratifikation', 'bonus', 'gehalt', 'marcel', 'melanie')):
            continue
        amount = next((_decimal_text(c) for c in cells if _decimal_text(c) is not None), None)
        if amount is None:
            continue
        bucket = _income_bucket(label)
        source_ref = f'R{row_number}:B-E'
        key = ('budget_plan', source_ref, bucket)
        if key in seen_keys:
            continue
        seen_keys.add(key)
        category_key = ('category', 'income', bucket)
        if category_key not in seen_keys:
            seen_keys.add(category_key)
            _insert_candidate(conn, _candidate_row(
                source_file_label=source_file_label,
                source_sheet='2026',
                source_row_or_range=source_ref,
                candidate_type='category',
                source_label=bucket,
                proposed_parent_label='Einnahmen',
                proposed_name=bucket,
                proposed_period_type=None,
                confidence='0.90',
                requires_review=False,
                notes='type:income; source:income_area_2026',
            ))
            counts['category'] += 1
        period = 'annual' if any(word in low for word in ('gratifikation', 'bonus', '13.')) else 'monthly'
        _insert_candidate(conn, _candidate_row(
            source_file_label=source_file_label,
            source_sheet='2026',
            source_row_or_range=source_ref,
            candidate_type='budget_plan',
            source_label=label,
            proposed_parent_label='Einnahmen',
            proposed_name=bucket,
            proposed_period_type=period,
            proposed_amount_text=amount,
            confidence='0.82' if bucket == 'Sonstige Einnahmen' else '0.90',
            requires_review=bucket == 'Sonstige Einnahmen',
            notes='type:income; source:income_area_2026',
        ))
        counts['budget_plan'] += 1


def seed_budget_candidates_from_rows(conn: Connection, sheets: dict[str, list[list[Any]]], *, source_file_label: str = "Budget workbook") -> dict[str, Any]:
    analysis = analyze_budget_workbook_rows(sheets)
    sheet = analysis.get("current_budget_sheet") or next(iter(sheets), "")
    rows = sheets.get(sheet, [])
    header_idx = None
    headers: list[str] = []
    for idx, row in enumerate(rows[:30]):
        normalised = [_normalise(cell) for cell in row]
        has_category = any(h in {"kategorie", "category", "hauptkategorie", "bereich"} for h in normalised)
        has_position = any(h in {"position", "beschreibung", "name", "detail", "unterkategorie", "was"} for h in normalised)
        has_budget = any(h in {"jan", "januar", "monatlich", "monthly", "jahr", "jaehrlich", "jährlich", "total", "summe"} for h in normalised)
        if (has_category or has_position) and has_budget:
            header_idx = idx
            headers = normalised
            break
    if header_idx is None:
        conn.commit()
        return analysis | {"category_candidate_count": 0, "budget_plan_candidate_count": 0, "recurring_candidate_count": 0}

    cat_idx = next((i for i, h in enumerate(headers) if h in {"kategorie", "category", "hauptkategorie", "bereich"}), None)
    pos_idx = next((i for i, h in enumerate(headers) if h in {"position", "beschreibung", "name", "detail", "unterkategorie", "was"}), None)
    if cat_idx is None:
        cat_idx = pos_idx
    type_indices = [i for i, h in enumerate(headers) if h in {"typ", "type", "art"}]
    conn.execute("DELETE FROM budget_seed_candidates WHERE source_file_label=? AND status IN ('pending','needs_review')", (source_file_label,))
    seen_categories: set[str] = set()
    seen_candidate_keys: set[tuple[str, str, str]] = set()
    current_parent = ""
    counts = {"category": 0, "budget_plan": 0, "recurring_candidate": 0, "unclean_range": 0}
    for row_number, row in enumerate(rows[header_idx + 1 :], start=header_idx + 2):
        category = str(row[cat_idx]).strip() if cat_idx is not None and cat_idx < len(row) and row[cat_idx] is not None else ""
        position = str(row[pos_idx]).strip() if pos_idx is not None and pos_idx < len(row) and row[pos_idx] is not None else ""
        if not category and not position:
            continue
        monthly = _find_amount(list(row), headers, "monthly")
        annual = _find_amount(list(row), headers, "annual")
        if monthly and not annual and any(h in {"monatlich", "monthly"} for h in headers):
            monthly = None
        source_ref = f"R{row_number}"
        is_section = bool(category and not monthly and not annual and (pos_idx == cat_idx or not position or position == category))
        if is_section:
            current_parent = category
        parent_label = current_parent if current_parent and not is_section else (category if cat_idx != pos_idx else None)
        if category and is_section and category not in seen_categories:
            seen_categories.add(category)
            _insert_candidate(conn, _candidate_row(source_file_label=source_file_label, source_sheet=sheet, source_row_or_range=source_ref, candidate_type="category", source_label=category, proposed_name=category, proposed_period_type=None, proposed_amount_text=None, confidence="0.90", requires_review=False, notes=f"type:{_category_type(category)}"))
            counts["category"] += 1
        elif category and cat_idx != pos_idx and category not in seen_categories:
            seen_categories.add(category)
            _insert_candidate(conn, _candidate_row(source_file_label=source_file_label, source_sheet=sheet, source_row_or_range=source_ref, candidate_type="category", source_label=category, proposed_name=category, proposed_period_type=None, proposed_amount_text=None, confidence="0.90", requires_review=False, notes=f"type:{_category_type(category)}"))
            counts["category"] += 1
        if monthly or annual:
            period = "monthly" if monthly else "annual"
            fixed_text = " ".join(_normalise(row[i]) for i in type_indices if i < len(row)) + " " + _normalise(position)
            is_fixed = any(hint in fixed_text for hint in ("fix", "fixkosten", "fixed", "wiederkehrend", "recurring", "abo", "miete"))
            _insert_candidate(conn, _candidate_row(source_file_label=source_file_label, source_sheet=sheet, source_row_or_range=source_ref, candidate_type="budget_plan", source_label=position or category, proposed_parent_label=parent_label, proposed_name=position or category, proposed_period_type=period, proposed_amount_text=monthly or annual, confidence="0.85", requires_review=False, notes="fixed_like" if is_fixed else None))
            seen_candidate_keys.add(("budget_plan", source_ref, position or category))
            counts["budget_plan"] += 1
            if is_fixed:
                _insert_candidate(conn, _candidate_row(source_file_label=source_file_label, source_sheet=sheet, source_row_or_range=source_ref, candidate_type="recurring_candidate", source_label=position or category, proposed_parent_label=parent_label, proposed_name=position or category, proposed_period_type="fixed_like", proposed_amount_text=monthly or annual, confidence="0.80", requires_review=True, notes="Nur vormerken; keine recurring_payments-Anlage"))
                counts["recurring_candidate"] += 1
        else:
            _insert_candidate(conn, _candidate_row(source_file_label=source_file_label, source_sheet=sheet, source_row_or_range=source_ref, candidate_type="unclean_range", source_label=position or category, proposed_name=position or category, proposed_period_type="unclear", confidence="0.20", requires_review=True, notes="Keine klaren Budgetwerte erkannt"))
            counts["unclean_range"] += 1
    if sheet == "2026":
        _income_candidates_from_2026_rows(conn, rows, source_file_label, counts, seen_candidate_keys)
    conn.commit()
    return analysis | {"category_candidate_count": counts["category"], "budget_plan_candidate_count": counts["budget_plan"], "recurring_candidate_count": counts["recurring_candidate"], "unclean_range_count": counts["unclean_range"], "income_candidate_count": conn.execute("SELECT COUNT(*) FROM budget_seed_candidates WHERE source_file_label=? AND candidate_type='budget_plan' AND notes LIKE '%type:income%'", (source_file_label,)).fetchone()[0]}


def list_seed_candidates(conn: Connection, *, status: str | None = None, candidate_type: str | None = None) -> list[dict[str, Any]]:
    clauses: list[str] = []
    params: list[object] = []
    if status:
        clauses.append("status=?")
        params.append(status)
    if candidate_type:
        clauses.append("candidate_type=?")
        params.append(candidate_type)
    where = "WHERE " + " AND ".join(clauses) if clauses else ""
    rows = conn.execute(f"SELECT * FROM budget_seed_candidates {where} ORDER BY created_at, source_sheet, source_row_or_range, candidate_type", params).fetchall()
    return [row_to_dict(row) | {"requires_review": bool(row["requires_review"])} for row in rows]


def _candidate(conn: Connection, seed_candidate_id: str) -> dict[str, Any]:
    row = conn.execute("SELECT * FROM budget_seed_candidates WHERE seed_candidate_id=?", (seed_candidate_id,)).fetchone()
    if not row:
        raise HTTPException(status_code=404, detail="seed candidate not found")
    return row_to_dict(row) | {"requires_review": bool(row["requires_review"])}


def _category_id_for_label(conn: Connection, label: str | None) -> str:
    if label:
        existing = conn.execute("SELECT category_id FROM budget_categories WHERE lower(name)=lower(?) AND is_active=1 ORDER BY sort_order LIMIT 1", (label,)).fetchone()
        if existing:
            return str(existing["category_id"])
    fallback = conn.execute("SELECT category_id FROM budget_categories WHERE category_id='bcat_review_needed'").fetchone()
    return str(fallback["category_id"])


def preview_seed_candidate(conn: Connection, seed_candidate_id: str, edits: dict[str, Any] | None = None) -> dict[str, Any]:
    candidate = _candidate(conn, seed_candidate_id)
    edits = edits or {}
    ctype = candidate["candidate_type"]
    if ctype == "unclean_range":
        return {"preview_id": new_id("preview"), "action": "review_unclean_range", "warnings": ["Manuelle Prüfung erforderlich"], "payload": {"seed_candidate_id": seed_candidate_id, "notes": edits.get("notes") or candidate.get("notes")}}
    name = str(edits.get("proposed_name") or candidate.get("proposed_name") or candidate.get("source_label") or "").strip()
    if ctype == "category":
        payload = {"name": name, "category_type": edits.get("category_type") or ("income" if "type:income" in str(candidate.get("notes") or "") or str(candidate.get("proposed_parent_label") or "").lower() == "einnahmen" else _category_type(name)), "parent_label": candidate.get("proposed_parent_label")}
        return create_category_preview(conn, payload) | {"action": "accept_category", "payload": {"seed_candidate_id": seed_candidate_id, **payload}}
    if ctype in {"budget_plan", "recurring_candidate"}:
        period = str(edits.get("proposed_period_type") or candidate.get("proposed_period_type") or "monthly")
        if period == "fixed_like":
            period = "monthly"
        category_id = str(edits.get("proposed_category_id") or candidate.get("proposed_category_id") or _category_id_for_label(conn, str(edits.get("proposed_parent_label") or candidate.get("proposed_parent_label") or "")))
        payload = {
            "plan_month": str(edits.get("plan_month") or "2026-01"),
            "category_id": category_id,
            "name": name,
            "monthly_amount_chf": candidate.get("proposed_amount_text") if period == "monthly" else None,
            "annual_amount_chf": candidate.get("proposed_amount_text") if period == "annual" else None,
            "cadence": "monthly" if period == "monthly" else "annual",
            "is_fixed_cost": ctype == "recurring_candidate" or candidate.get("notes") == "fixed_like",
            "source_type": "excel_seed_dry_run",
            "notes": "Budget Seed Review 2026",
        }
        return create_budget_plan_item_preview(conn, payload) | {"action": "accept_budget_plan", "payload": {"seed_candidate_id": seed_candidate_id, **payload}}
    raise HTTPException(status_code=422, detail="unsupported seed candidate type")


def confirm_seed_candidate(conn: Connection, seed_candidate_id: str, payload: dict[str, Any]) -> dict[str, Any]:
    candidate = _candidate(conn, seed_candidate_id)
    if candidate["status"] in {"confirmed", "ignored"}:
        raise HTTPException(status_code=409, detail="seed candidate already closed")
    ctype = candidate["candidate_type"]
    payload = {k: v for k, v in payload.items() if k != "seed_candidate_id"}
    if ctype == "category":
        result = confirm_create_category(conn, payload)
    elif ctype in {"budget_plan", "recurring_candidate"}:
        result = confirm_budget_plan_item(conn, payload)
    else:
        raise HTTPException(status_code=422, detail="unclean range cannot be confirmed automatically")
    ts = now()
    conn.execute("UPDATE budget_seed_candidates SET status='confirmed', updated_at=?, proposed_category_id=COALESCE(?, proposed_category_id) WHERE seed_candidate_id=?", (ts, payload.get("category_id"), seed_candidate_id))
    audit_id = record_audit_event(conn, source="vue_dashboard", action="confirm", entity_type="budget_seed_candidate", entity_id=seed_candidate_id, old_values={"status": candidate["status"]}, new_values={"status": "confirmed", "linked_entity_id": result["entity_id"]}, created_by="user")
    conn.commit()
    return {"status": "confirmed", "entity_id": result["entity_id"], "audit_id": audit_id, "message": "Seed-Kandidat übernommen"}


def ignore_seed_candidate(conn: Connection, seed_candidate_id: str, note: str | None = None) -> dict[str, Any]:
    candidate = _candidate(conn, seed_candidate_id)
    ts = now()
    conn.execute("UPDATE budget_seed_candidates SET status='ignored', notes=COALESCE(?, notes), updated_at=? WHERE seed_candidate_id=?", (note, ts, seed_candidate_id))
    audit_id = record_audit_event(conn, source="vue_dashboard", action="ignore", entity_type="budget_seed_candidate", entity_id=seed_candidate_id, old_values={"status": candidate["status"]}, new_values={"status": "ignored", "note": note}, created_by="user")
    conn.commit()
    return {"status": "ignored", "entity_id": seed_candidate_id, "audit_id": audit_id, "message": "Seed-Kandidat ignoriert"}


def set_seed_candidate_status(conn: Connection, seed_candidate_id: str, status: str, note: str | None = None) -> dict[str, Any]:
    if status not in STATUSES:
        raise HTTPException(status_code=422, detail="invalid candidate status")
    candidate = _candidate(conn, seed_candidate_id)
    ts = now()
    conn.execute(
        "UPDATE budget_seed_candidates SET status=?, notes=COALESCE(?, notes), updated_at=? WHERE seed_candidate_id=?",
        (status, note, ts, seed_candidate_id),
    )
    audit_id = record_audit_event(
        conn,
        source="vue_dashboard",
        action="status_change",
        entity_type="budget_seed_candidate",
        entity_id=seed_candidate_id,
        old_values={"status": candidate["status"]},
        new_values={"status": status, "note": note},
        created_by="user",
    )
    conn.commit()
    return {"status": status, "entity_id": seed_candidate_id, "audit_id": audit_id, "message": "Seed-Kandidat Status aktualisiert"}


def reopen_seed_candidate(conn: Connection, seed_candidate_id: str, note: str | None = None) -> dict[str, Any]:
    return set_seed_candidate_status(conn, seed_candidate_id, "pending", note or "Vom User wieder geöffnet")


def convert_unclean_range(conn: Connection, seed_candidate_id: str, target_type: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
    payload = payload or {}
    candidate = _candidate(conn, seed_candidate_id)
    if candidate["candidate_type"] != "unclean_range":
        raise HTTPException(status_code=422, detail="only unclean ranges can be converted")
    if target_type not in {"category", "budget_plan", "income", "transaction_candidate"}:
        raise HTTPException(status_code=422, detail="invalid target type")
    if target_type == "transaction_candidate":
        stats = seed_transaction_candidates_from_rows(
            conn,
            [[payload.get("transaction_date") or "", payload.get("description") or candidate.get("proposed_name") or candidate.get("source_label") or "", payload.get("amount") or candidate.get("proposed_amount_text") or ""]],
            source_file_label=str(candidate.get("source_file_label") or "Unclean Range"),
            source_type="unclean_range",
        )
        audit_id = record_audit_event(
            conn,
            source="vue_dashboard",
            action="convert_unclean_range",
            entity_type="budget_seed_candidate",
            entity_id=seed_candidate_id,
            old_values={"candidate_type": "unclean_range", "status": candidate["status"]},
            new_values={"target_type": target_type, "candidate_count": stats.get("candidate_count")},
            created_by="user",
        )
        conn.commit()
        return {"status": "created", "entity_id": seed_candidate_id, "audit_id": audit_id, "message": "Buchungskandidat aus Unclean Range angelegt", **stats}
    seed_target_type = "budget_plan" if target_type == "income" else target_type
    name = str(payload.get("proposed_name") or candidate.get("proposed_name") or candidate.get("source_label") or "").strip()
    if not name:
        raise HTTPException(status_code=422, detail="proposed_name is required")
    row = _candidate_row(
        source_file_label=candidate.get("source_file_label"),
        source_sheet=candidate.get("source_sheet"),
        source_row_or_range=candidate.get("source_row_or_range"),
        candidate_type=seed_target_type,
        source_label=candidate.get("source_label"),
        proposed_parent_label=payload.get("proposed_parent_label") or candidate.get("proposed_parent_label"),
        proposed_name=name,
        proposed_period_type=payload.get("proposed_period_type") or ("monthly" if seed_target_type == "budget_plan" else None),
        proposed_amount_text=payload.get("proposed_amount_text") or candidate.get("proposed_amount_text"),
        confidence=payload.get("confidence") or "0.55",
        requires_review=True,
        notes=f"converted_from_unclean:{seed_candidate_id}; {'type:income; ' if target_type == 'income' else ''}{payload.get('notes') or candidate.get('notes') or ''}".strip(),
    )
    _insert_candidate(conn, row)
    audit_id = record_audit_event(
        conn,
        source="vue_dashboard",
        action="convert_unclean_range",
        entity_type="budget_seed_candidate",
        entity_id=seed_candidate_id,
        old_values={"candidate_type": "unclean_range", "status": candidate["status"]},
        new_values={"created_candidate_id": row["seed_candidate_id"], "target_type": target_type},
        created_by="user",
    )
    conn.commit()
    return {"status": "created", "entity_id": row["seed_candidate_id"], "audit_id": audit_id, "message": "Review-Kandidat aus Unclean Range angelegt"}


def preview_clear_category_takeover(conn: Connection) -> dict[str, Any]:
    rows = [c for c in list_seed_candidates(conn, candidate_type="category") if c["status"] not in {"confirmed", "ignored"} and not c["requires_review"]]
    return {"preview_id": new_id("preview"), "summary": "Eindeutige Kategorien übernehmen", "count": len(rows), "categories": [{"seed_candidate_id": r["seed_candidate_id"], "name": r.get("proposed_name") or r.get("source_label"), "category_type": ("income" if "type:income" in str(r.get("notes") or "") or str(r.get("proposed_parent_label") or "").lower() == "einnahmen" else _category_type(str(r.get("proposed_name") or r.get("source_label") or ""))), "parent_label": r.get("proposed_parent_label"), "status": r["status"]} for r in rows], "warnings": []}


def confirm_clear_category_takeover(conn: Connection) -> dict[str, Any]:
    preview = preview_clear_category_takeover(conn)
    confirmed = 0
    skipped = 0
    audit_ids: list[str] = []
    for item in preview["categories"]:
        candidate = _candidate(conn, item["seed_candidate_id"])
        result = confirm_create_category(conn, {"name": item["name"], "category_type": item["category_type"], "parent_label": item.get("parent_label")})
        ts = now()
        conn.execute("UPDATE budget_seed_candidates SET status='confirmed', proposed_category_id=?, updated_at=? WHERE seed_candidate_id=?", (result["entity_id"], ts, item["seed_candidate_id"]))
        audit_ids.append(record_audit_event(conn, source="vue_dashboard", action="confirm", entity_type="budget_seed_candidate", entity_id=item["seed_candidate_id"], old_values={"status": candidate["status"]}, new_values={"status": "confirmed", "linked_entity_id": result["entity_id"]}, created_by="user"))
        confirmed += 1
    conn.commit()
    return {"status": "confirmed", "entity_id": "budget_category_takeover", "audit_id": audit_ids[-1] if audit_ids else "", "message": "Eindeutige Kategorien übernommen", "confirmed_count": confirmed, "skipped_count": skipped}


def preview_budget_plan_takeover(conn: Connection) -> dict[str, Any]:
    rows = [c for c in list_seed_candidates(conn, candidate_type="budget_plan") if c["status"] not in {"ignored"}]
    items = []
    for row in rows:
        try:
            preview = preview_seed_candidate(conn, row["seed_candidate_id"], {})
            items.append({"seed_candidate_id": row["seed_candidate_id"], "name": row.get("proposed_name"), "category_id": preview["payload"].get("category_id"), "period_type": row.get("proposed_period_type"), "status": row["status"]})
        except HTTPException:
            items.append({"seed_candidate_id": row["seed_candidate_id"], "name": row.get("proposed_name"), "category_id": None, "period_type": row.get("proposed_period_type"), "status": row["status"], "warning": "review_required"})
    return {"preview_id": new_id("preview"), "summary": "Bestätigte Budgetpläne übernehmen", "count": len(items), "budget_plans": items, "warnings": []}


def confirm_budget_plan_takeover(conn: Connection) -> dict[str, Any]:
    rows = [c for c in list_seed_candidates(conn, candidate_type="budget_plan") if c["status"] not in {"ignored"}]
    confirmed = 0
    skipped = 0
    audit_ids: list[str] = []
    for row in rows:
        if row["status"] == "confirmed":
            skipped += 1
            continue
        preview = preview_seed_candidate(conn, row["seed_candidate_id"], {})
        result = confirm_seed_candidate(conn, row["seed_candidate_id"], preview["payload"])
        if result.get("audit_id"):
            audit_ids.append(result["audit_id"])
        confirmed += 1
    return {"status": "confirmed", "entity_id": "budget_plan_takeover", "audit_id": audit_ids[-1] if audit_ids else "", "message": "Budgetpläne übernommen", "confirmed_count": confirmed, "skipped_count": skipped}
