from __future__ import annotations

from decimal import Decimal

from jarvis_finance.dashboard import data
from jarvis_finance.dashboard.components.tables import show_table
from jarvis_finance.equity.manage import add_equity_initial_snapshot, add_equity_transaction, add_manual_position_from_catalog, create_manual_catalog_entry, ensure_standard_broker_accounts, get_equity_management_options, search_instrument_candidates
from jarvis_finance.fx.overrides import set_manual_fx_override
from jarvis_finance.imports.instrument_candidates import confirm_candidate, defer_candidate, qwen_resolve_staged_candidates, reject_candidate, resolve_candidate
from jarvis_finance.market_data.candidates import reject_mapping_candidate, select_mapping_candidate

PAGE_TITLE = "Equity/ETF Manage"


def _account_label(row: dict[str, str]) -> str:
    return f"{row['platform_name']} / {row['account_name']} [{row['currency']} ]"


def _instrument_label(row: dict[str, str]) -> str:
    suffix = row.get("isin") or row.get("ticker") or row["instrument_id"]
    return f"{row['name']} ({suffix})"


def _selected(rows: list[dict[str, str]], label: str | None, label_fn) -> dict[str, str] | None:
    if not label:
        return None
    for row in rows:
        if label_fn(row) == label:
            return row
    return None


def render(st, conn) -> None:
    st.title("Equity/ETF Manage")
    st.caption("Auditierbare manuelle Aktien-/ETF-Erfassung. Dashboard liest lokal; keine Broker-Dateien oder Live-APIs beim Rendern.")
    st.warning("Keine stillen Überschreibungen: Initial Snapshots, Transaktionen und Korrekturen werden als bestätigte Ledger-Ereignisse mit Audit-Log gespeichert.")

    options = get_equity_management_options(conn)
    account_labels = [_account_label(row) for row in options["accounts"]]
    instrument_labels = [_instrument_label(row) for row in options["instruments"]]
    tabs = st.tabs(["Instrument Search / Add Position", "Mapping Review Queue", "Hinzufügen", "Kaufen", "Partial Sell", "Full Sell", "Dividende", "ETF-Ausschüttung", "Fee", "Korrigieren", "Review"])

    with tabs[0]:
        st.subheader("Aktie/ETF hinzufügen")
        st.caption("Wizard: 1 Suchen → 2 Instrument auswählen → 3 Depot & Menge → 4 Einstand/Datum/Notiz → 5 Review → 6 Bestätigen.")
        st.info("DOCX-Brokerimporte sind hier bewusst nicht der Hauptpfad. Es wird nur auf Klick gesucht; kein Provider-/Live-API-Call beim Rendern.")
        ensure_standard_broker_accounts(conn)

        st.subheader("1. Instrument suchen")
        query = st.text_input("ISIN, Name oder Ticker", key="wizard_search_query")
        class_filter = st.selectbox("Assetklasse", ["", "stock", "etf"], key="wizard_search_class")
        currency_filter = st.selectbox("Währung", ["", "CHF", "EUR", "USD"], key="wizard_search_currency")
        exchange_filter = st.text_input("Börse/Exchange optional", key="wizard_search_exchange")
        country_filter = st.text_input("Land optional", key="wizard_search_country")
        provider_filter = st.text_input("Provider optional", key="wizard_search_provider")
        external_lookup = st.checkbox("Provider-Suche explizit versuchen", key="wizard_search_external")
        if st.button("Suchen", key="wizard_search_button"):
            search_payload = search_instrument_candidates(
                conn,
                query=query,
                asset_class=class_filter or None,
                exchange=exchange_filter or None,
                currency=currency_filter or None,
                country=country_filter or None,
                provider=provider_filter or None,
                include_external=bool(external_lookup),
            )
            if hasattr(st, "session_state"):
                st.session_state["wizard_search_results"] = search_payload
        search_state = getattr(st, "session_state", {}).get("wizard_search_results", {"results": [], "warnings": [], "selection_required": True})
        if search_state.get("warnings"):
            st.warning("; ".join(str(w) for w in search_state.get("warnings", [])))
        display_rows = [
            {
                "Name": r.get("name", ""),
                "ISIN": r.get("isin", ""),
                "Ticker": r.get("ticker", ""),
                "Exchange": r.get("exchange", ""),
                "Währung": r.get("currency", ""),
                "Assetklasse": r.get("asset_class", ""),
                "Datenquelle": r.get("data_source", ""),
                "Mapping-Status": r.get("mapping_status", ""),
                "Letzter Kurs": r.get("last_price", ""),
                "Kursdatum": r.get("price_date", ""),
            }
            for r in search_state.get("results", [])
        ]
        show_table(st, display_rows, empty_message="Noch keine Suche ausgeführt oder kein lokaler Treffer. Manuelle Anlage ist möglich.")

        st.subheader("2. Instrument auswählen oder manuell anlegen")
        result_labels = [f"{i+1}. {r.get('name')} | {r.get('isin') or 'ISIN fehlt'} | {r.get('ticker') or 'Ticker fehlt'} | {r.get('exchange') or 'Exchange fehlt'} | {r.get('currency') or 'Währung fehlt'}" for i, r in enumerate(search_state.get("results", []))]
        chosen_label = st.selectbox("Treffer bewusst auswählen", result_labels, key="wizard_selected_result") if result_labels else None
        selected_result = None
        if chosen_label:
            selected_result = search_state.get("results", [])[int(chosen_label.split('.', 1)[0]) - 1]
            st.success("Instrument ausgewählt. Ticker allein wurde nicht automatisch übernommen; Auswahl ist bewusst erfolgt.")
        with st.expander("Manuell anlegen, falls kein sauberer Treffer vorhanden ist"):
            c_asset = st.selectbox("Assetklasse", ["stock", "etf"], key="wizard_manual_asset")
            c_name = st.text_input("Name", key="wizard_manual_name")
            c_isin = st.text_input("ISIN stark empfohlen", key="wizard_manual_isin")
            c_ticker = st.text_input("Ticker", key="wizard_manual_ticker")
            c_exchange = st.text_input("Exchange", key="wizard_manual_exchange")
            c_currency = st.selectbox("Währung", ["CHF", "EUR", "USD"], key="wizard_manual_currency")
            c_note = st.text_area("Notiz / Quelle zur manuellen Anlage", key="wizard_manual_note")
            st.warning("Manuelle Anlage ohne ISIN erzeugt einen Data-Quality-Hinweis; ISIN und Ticker dürfen nicht beide leer sein.")
            if st.button("Manuelles Instrument speichern", key="wizard_manual_save"):
                try:
                    if not c_isin and not c_ticker:
                        raise ValueError("ISIN oder Ticker ist erforderlich")
                    create_manual_catalog_entry(conn, asset_class=c_asset, name=c_name, currency=c_currency, isin=c_isin, ticker=c_ticker, exchange=c_exchange, note=c_note)
                    st.success("Instrument im lokalen Katalog gespeichert. Bitte anschließend erneut suchen und bewusst auswählen.")
                except Exception as exc:
                    st.error(f"Manuelle Anlage abgelehnt: {exc}")

        st.subheader("3–4. Depot, Menge, Datum, Einstand und Notiz")
        pos_account_label = st.selectbox("Plattform/Depot", account_labels, key="wizard_position_account") if account_labels else None
        pos_type = st.selectbox("Buchung", ["initial_snapshot", "buy", "partial_sell", "full_sell", "dividend", "etf_distribution", "manual_adjustment"], key="wizard_position_type")
        pos_qty = st.text_input("Menge als Decimal-Text", key="wizard_position_qty")
        pos_date = st.date_input("Snapshot-/Buchungsdatum", key="wizard_position_date")
        default_currency = selected_result.get("currency") if selected_result else "CHF"
        pos_currency = st.selectbox("Originalwährung", ["CHF", "EUR", "USD"], index=["CHF", "EUR", "USD"].index(default_currency) if default_currency in {"CHF", "EUR", "USD"} else 0, key="wizard_position_currency")
        pos_cost = st.text_input("Einstand / Brutto optional — leer = Cost Basis ungewiss", key="wizard_position_cost")
        pos_fx_status = st.selectbox("FX-Status", ["not_needed", "ok", "missing", "manual_override", "manual_override_required"], key="wizard_position_fx_status")
        pos_fx_rate = st.text_input("FX-Kurs zu CHF optional", key="wizard_position_fx_rate")
        pos_fx_source = st.text_input("FX-Quelle optional", key="wizard_position_fx_source")
        pos_note = st.text_area("Notiz — Pflicht bei unvollständiger Historie oder manual_adjustment", key="wizard_position_note")

        st.subheader("5. Review")
        review = {
            "Instrument": selected_result.get("name") if selected_result else "noch nicht ausgewählt",
            "ISIN": selected_result.get("isin") if selected_result else "",
            "Ticker": selected_result.get("ticker") if selected_result else "",
            "Exchange": selected_result.get("exchange") if selected_result else "",
            "Depot": pos_account_label or "",
            "Buchung": pos_type,
            "Datum": str(pos_date),
            "Cost Basis": "ungewiss" if not pos_cost else "erfasst",
            "FX": pos_fx_status,
        }
        st.write(review)
        pos_confirm = st.checkbox("Ich bestätige: genau diese Position/Transaktion speichern", key="wizard_position_confirm")
        selected_pos_account = _selected(options["accounts"], pos_account_label, _account_label)
        st.subheader("6. Bestätigen")
        if st.button("Bestätigt speichern", key="wizard_position_save", type="primary"):
            try:
                if not selected_result or not selected_result.get("catalog_entry_id"):
                    raise ValueError("Instrument selection is required before saving")
                if not selected_pos_account:
                    raise ValueError("account is required")
                result = add_manual_position_from_catalog(
                    conn,
                    catalog_entry_id=selected_result["catalog_entry_id"],
                    account_id=selected_pos_account["account_id"],
                    position_type=pos_type,
                    quantity_text=pos_qty,
                    trade_date=str(pos_date),
                    currency=pos_currency,
                    cost_basis_original_text=pos_cost or None,
                    fx_status=pos_fx_status,
                    fx_rate_to_chf_text=pos_fx_rate or None,
                    fx_source=pos_fx_source or None,
                    note=pos_note,
                    confirm=bool(pos_confirm),
                )
                st.success("Gespeichert: Ledger-Ereignis und Audit-Log wurden erzeugt.")
                if result.warnings:
                    st.warning("Datenqualität: " + "; ".join(result.warnings))
            except Exception as exc:
                st.error(f"Speicherung abgelehnt: {exc}")

    with tabs[1]:
        st.subheader("Mapping Review Queue: PostFinance / True Wealth")
        st.caption("Review-only: Bestätigung schreibt nur Kandidatenstatus + Audit-Log. Kein Initial-Snapshot-Import aus dieser Ansicht.")
        summary = data.get_instrument_import_candidate_summary(conn)
        st.write(summary)
        if st.button("Qwen Review-Hilfe auf sanitizte Kandidaten anwenden", key="instrument_candidate_qwen_resolve"):
            try:
                result = qwen_resolve_staged_candidates(conn)
                st.success("Qwen Review-Hilfe ausgeführt; keine Buchung erstellt.")
                st.write({"qwen_available": result.qwen_available, "model": result.model, "improved": result.improved, "manual_review_remaining": result.manual_review_remaining})
            except Exception as exc:
                st.error(f"Qwen Review-Hilfe abgelehnt: {exc}")
        safe_rows = data.get_instrument_import_candidates(conn, status="exact_isin_match")
        review_rows = [r for r in data.get_instrument_import_candidates(conn) if r.get("mapping_status") in {"needs_manual_review", "probable"}]
        with st.expander("Gruppe 1: Sichere ISIN-Treffer — bereit zur Bestätigung", expanded=True):
            st.caption("Anzeige ohne Mengen/Werte. Exact-ISIN bedeutet review-bereit, nicht importiert.")
            show_table(st, safe_rows)
            safe_labels = [f"{r['candidate_id']} | {r['platform']} | {r.get('proposed_ticker') or r.get('raw_ticker') or ''} | {r.get('proposed_isin') or r.get('raw_isin') or ''}" for r in safe_rows]
            safe_selected = st.selectbox("Sicheren ISIN-Treffer auswählen", safe_labels, key="instrument_candidate_safe_select") if safe_labels else None
            safe_note = st.text_area("Review-Notiz für sichere ISIN-Treffer", key="instrument_candidate_safe_note")
            if st.button("Sicheren Treffer bestätigen", key="instrument_candidate_safe_confirm"):
                try:
                    if not safe_selected:
                        raise ValueError("candidate is required")
                    confirm_candidate(conn, safe_selected.split(" | ", 1)[0], note=safe_note or "Exact-ISIN Treffer manuell bestätigt")
                    st.success("Kandidat bestätigt; Audit-Log geschrieben. Kein Portfolio-Import.")
                except Exception as exc:
                    st.error(f"Bestätigung abgelehnt: {exc}")
            if st.button("Sicheren Treffer ablehnen", key="instrument_candidate_safe_reject"):
                try:
                    if not safe_selected:
                        raise ValueError("candidate is required")
                    reject_candidate(conn, safe_selected.split(" | ", 1)[0], note=safe_note or "Exact-ISIN Treffer abgelehnt")
                    st.success("Kandidat abgelehnt; Audit-Log geschrieben.")
                except Exception as exc:
                    st.error(f"Ablehnung abgelehnt: {exc}")
        with st.expander("Gruppe 2: Prüfung nötig — ISIN/Exchange/Währung ergänzen", expanded=True):
            st.caption("Ticker allein bleibt blockiert. Mengen/Werte werden nicht angezeigt.")
            show_table(st, review_rows)
            labels = [f"{r['candidate_id']} | {r['platform']} | {r.get('proposed_ticker') or r.get('raw_ticker') or ''} | {r.get('proposed_isin') or r.get('raw_isin') or ''} | {r['mapping_status']}" for r in review_rows]
            selected_label = st.selectbox("Kandidat auswählen", labels, key="instrument_candidate_select") if labels else None
            review_note = st.text_area("Review-Notiz", key="instrument_candidate_note")
            manual_isin = st.text_input("ISIN ergänzen/korrigieren", key="instrument_candidate_isin")
            manual_ticker = st.text_input("Ticker korrigieren", key="instrument_candidate_ticker")
            manual_exchange = st.text_input("Exchange setzen", key="instrument_candidate_exchange")
            manual_currency = st.selectbox("Währung korrigieren", ["", "CHF", "EUR", "USD"], key="instrument_candidate_currency")
            st.info("Als bestätigt speichern bedeutet nur: Status confirmed + Audit-Log. Produktiver Initial Snapshot bleibt blockiert bis explizite Freigabe.")
            if st.button("Resolver erneut anwenden", key="instrument_candidate_resolve"):
                try:
                    if not selected_label:
                        raise ValueError("candidate is required")
                    result = resolve_candidate(conn, selected_label.split(" | ", 1)[0])
                    st.success("Resolver ausgeführt; Status aktualisiert.")
                    st.write(result)
                except Exception as exc:
                    st.error(f"Resolver abgelehnt: {exc}")
            if st.button("Als bestätigt speichern", key="instrument_candidate_confirm"):
                try:
                    if not selected_label:
                        raise ValueError("candidate is required")
                    confirm_candidate(conn, selected_label.split(" | ", 1)[0], note=review_note, proposed_isin=manual_isin or None, proposed_ticker=manual_ticker or None, proposed_exchange=manual_exchange or None, proposed_currency=manual_currency or None)
                    st.success("Kandidat bestätigt; Audit-Log geschrieben. Kein Portfolio-Import.")
                except Exception as exc:
                    st.error(f"Bestätigung abgelehnt: {exc}")
            if st.button("Zurückstellen", key="instrument_candidate_defer"):
                try:
                    if not selected_label:
                        raise ValueError("candidate is required")
                    defer_candidate(conn, selected_label.split(" | ", 1)[0], note=review_note or "später prüfen")
                    st.success("Kandidat zurückgestellt; Audit-Log geschrieben.")
                except Exception as exc:
                    st.error(f"Zurückstellen abgelehnt: {exc}")
            if st.button("Ablehnen", key="instrument_candidate_reject"):
                try:
                    if not selected_label:
                        raise ValueError("candidate is required")
                    reject_candidate(conn, selected_label.split(" | ", 1)[0], note=review_note)
                    st.success("Kandidat abgelehnt; Audit-Log geschrieben.")
                except Exception as exc:
                    st.error(f"Ablehnung abgelehnt: {exc}")
        st.caption("Finaler Initial-Snapshot-Import bleibt absichtlich außerhalb dieser Review-Aktion und braucht separate Freigabe.")

    with tabs[2]:
        st.subheader("Aktie/ETF als Initial Snapshot hinzufügen")
        account_label = st.selectbox("Depot/Konto", account_labels, key="equity_add_account") if account_labels else None
        asset_class = st.selectbox("Assetklasse", ["stock", "ETF"], key="equity_add_class")
        name = st.text_input("Name", key="equity_add_name")
        isin = st.text_input("ISIN (primäre Identifikation)", key="equity_add_isin")
        ticker = st.text_input("Ticker optional", key="equity_add_ticker")
        exchange = st.text_input("Exchange optional", key="equity_add_exchange")
        currency = st.selectbox("Währung", ["CHF", "EUR", "USD"], key="equity_add_currency")
        quantity = st.text_input("Menge als Decimal-Text", key="equity_add_qty")
        cost_basis = st.text_input("Einstandspreis / Cost Basis Originalwährung", key="equity_add_cost")
        fx_rate = st.text_input("FX-Kurs zu CHF (bei EUR/USD)", key="equity_add_fx")
        fx_source = st.text_input("FX-Quelle", key="equity_add_fx_source")
        fx_status = st.selectbox("FX-Status", ["ok", "missing"], key="equity_add_fx_status")
        snapshot_date = st.date_input("Snapshot-Datum", key="equity_add_date")
        category = st.selectbox("Kategorie", ["Core", "Opportunity", "Unknown"], key="equity_add_category")
        note = st.text_area("Notiz (Pflicht bei unvollständiger Historie)", key="equity_add_note")
        with st.expander("ETF-spezifische Felder"):
            ter = st.text_input("TER optional", key="equity_add_ter")
            distribution_policy = st.text_input("ausschüttend/thesaurierend optional", key="equity_add_distribution")
            index_name = st.text_input("Index optional", key="equity_add_index")
            fund_domicile = st.text_input("Fondsdomizil optional", key="equity_add_domicile")
            benchmark = st.text_input("Benchmark optional", key="equity_add_benchmark")
        confirm = st.checkbox("Ich bestätige diese einzelne Änderung", key="equity_add_confirm")
        selected_account = _selected(options["accounts"], account_label, _account_label)
        st.write({"Aktion": "initial_position_snapshot", "Name": name, "ISIN": isin or "<fehlt>", "Ticker": ticker or "<fehlt>", "Währung": currency, "Datum": str(snapshot_date), "Hinweis": "Keine Massenänderung; Speicherung erst nach Bestätigung."})
        if st.button("Bestätigt speichern", key="equity_add_save", type="primary"):
            try:
                if not selected_account:
                    raise ValueError("account is required")
                result = add_equity_initial_snapshot(
                    conn,
                    account_id=selected_account["account_id"],
                    asset_class=asset_class,
                    name=name,
                    isin=isin,
                    ticker=ticker,
                    exchange=exchange,
                    currency=currency,
                    quantity_text=quantity,
                    cost_basis_original_text=cost_basis,
                    snapshot_date=str(snapshot_date),
                    category=category,
                    note=note,
                    fx_rate_to_chf_text=fx_rate or None,
                    fx_source=fx_source or None,
                    fx_status=fx_status,
                    ter_text=ter or None,
                    distribution_policy=distribution_policy or None,
                    index_name=index_name or None,
                    fund_domicile=fund_domicile or None,
                    benchmark=benchmark or None,
                    confirm=bool(confirm),
                )
                st.success("Initial Snapshot gespeichert und auditierbar erfasst.")
                if result.warnings:
                    st.warning("Datenqualität: " + "; ".join(result.warnings))
            except Exception as exc:
                st.error(f"Speichern abgelehnt: {exc}")

    def transaction_form(tab, title: str, tx_type: str) -> None:
        with tab:
            st.subheader(title)
            account_label = st.selectbox("Depot/Konto", account_labels, key=f"equity_{tx_type}_account") if account_labels else None
            instrument_label = st.selectbox("Instrument", instrument_labels, key=f"equity_{tx_type}_instrument") if instrument_labels else None
            date_value = st.date_input("Datum", key=f"equity_{tx_type}_date")
            qty_default = "0" if tx_type in {"dividend", "etf_distribution", "fee"} else ""
            quantity = st.text_input("Menge als Decimal-Text", value=qty_default, key=f"equity_{tx_type}_qty")
            gross = st.text_input("Brutto/Cost/Proceeds Originalwährung", key=f"equity_{tx_type}_gross")
            currency = st.selectbox("Währung", ["CHF", "EUR", "USD"], key=f"equity_{tx_type}_currency")
            fx_rate = st.text_input("FX-Kurs zu CHF (bei EUR/USD)", key=f"equity_{tx_type}_fx")
            fx_source = st.text_input("FX-Quelle", key=f"equity_{tx_type}_fx_source")
            fx_status = st.selectbox("FX-Status", ["ok", "missing"], key=f"equity_{tx_type}_fx_status")
            note = st.text_area("Notiz / Grund", key=f"equity_{tx_type}_note")
            confirm = st.checkbox("Ich bestätige diese Änderung", key=f"equity_{tx_type}_confirm")
            selected_account = _selected(options["accounts"], account_label, _account_label)
            selected_instrument = _selected(options["instruments"], instrument_label, _instrument_label)
            st.write({"Aktion": tx_type, "Instrument": instrument_label, "Datum": str(date_value), "FX-Status": fx_status})
            if st.button("Bestätigt speichern", key=f"equity_{tx_type}_save"):
                try:
                    if not selected_account or not selected_instrument:
                        raise ValueError("account and instrument are required")
                    result = add_equity_transaction(
                        conn,
                        account_id=selected_account["account_id"],
                        instrument_id=selected_instrument["instrument_id"],
                        transaction_type=tx_type,
                        trade_date=str(date_value),
                        quantity_text=quantity,
                        gross_amount_original_text=gross,
                        currency=currency,
                        fx_rate_to_chf_text=fx_rate or None,
                        fx_source=fx_source or None,
                        fx_status=fx_status,
                        note=note,
                        confirm=bool(confirm),
                    )
                    st.success("Transaktion gespeichert und auditierbar erfasst.")
                    if result.warnings:
                        st.warning("Datenqualität: " + "; ".join(result.warnings))
                except Exception as exc:
                    st.error(f"Speichern abgelehnt: {exc}")

    transaction_form(tabs[3], "Kauf erfassen", "buy")
    transaction_form(tabs[4], "Teilverkauf erfassen", "partial_sell")
    transaction_form(tabs[5], "Vollverkauf erfassen", "full_sell")
    transaction_form(tabs[6], "Dividende erfassen", "dividend")
    transaction_form(tabs[7], "ETF-Ausschüttung erfassen", "etf_distribution")
    transaction_form(tabs[8], "Fee erfassen", "fee")
    transaction_form(tabs[9], "Korrektur als manual_adjustment", "manual_adjustment")

    with tabs[10]:
        st.subheader("Aktuelle Equity/ETF Übersicht")
        show_table(st, data.get_positions(conn))
        st.divider()
        st.subheader("Instrument Price Mapping Review")
        st.caption("Runtime-Review: Kandidaten ansehen und manuell bestätigen. Keine Live-API und keine Positionsänderung beim Rendern.")
        review_rows = data.get_instrument_mapping_review(conn)
        show_table(st, review_rows)
        candidates_all = data.get_instrument_mapping_candidates(conn)
        provider_filter = st.selectbox("Filter Provider", [""] + sorted({c.get("candidate_provider", "") for c in candidates_all if c.get("candidate_provider")}), key="mapping_candidate_provider_filter") if candidates_all else ""
        exchange_filter = st.selectbox("Filter Exchange", [""] + sorted({c.get("candidate_exchange", "") for c in candidates_all if c.get("candidate_exchange")}), key="mapping_candidate_exchange_filter") if candidates_all else ""
        currency_filter = st.selectbox("Filter Währung", [""] + sorted({c.get("candidate_currency", "") for c in candidates_all if c.get("candidate_currency")}), key="mapping_candidate_currency_filter") if candidates_all else ""
        confidence_filter = st.selectbox("Filter Confidence", ["", "high", "medium", "low"], key="mapping_candidate_confidence_filter") if candidates_all else ""
        hedge_filter = st.selectbox("Filter Hedge-Status", [""] + sorted({c.get("candidate_hedge_status", "") for c in candidates_all if c.get("candidate_hedge_status")}), key="mapping_candidate_hedge_filter") if candidates_all else ""
        candidates = [c for c in candidates_all if (not provider_filter or c.get("candidate_provider") == provider_filter) and (not exchange_filter or c.get("candidate_exchange") == exchange_filter) and (not currency_filter or c.get("candidate_currency") == currency_filter) and (not confidence_filter or c.get("confidence") == confidence_filter) and (not hedge_filter or c.get("candidate_hedge_status") == hedge_filter)]
        show_table(st, candidates)
        candidate_labels = [f"{c['candidate_id']} | {c['candidate_provider']} | {c['candidate_provider_symbol']} | {c['candidate_exchange']} | {c['candidate_currency']} | score={c.get('ranking_score','')} | {c.get('ranking_mark','')} | {c['confidence']} | {c['review_status']}" for c in candidates]
        selected_candidate_label = st.selectbox("Mapping-Kandidat auswählen", candidate_labels, key="mapping_candidate_select") if candidate_labels else None
        mapping_note = st.text_area("Bestätigungsnotiz für Mapping-Auswahl", key="mapping_candidate_note")
        st.info("Aktionen: Kandidat auswählen, Kandidat ablehnen, manuell erfassen über Tab 'Instrument Search / Add Position', oder später prüfen durch Nicht-Auswahl.")
        if st.button("Ausgewählten Mapping-Kandidaten bestätigen", key="mapping_candidate_confirm"):
            try:
                if not selected_candidate_label:
                    raise ValueError("candidate is required")
                candidate_id = selected_candidate_label.split(" | ", 1)[0]
                mapping_id = select_mapping_candidate(conn, candidate_id=candidate_id, note=mapping_note, created_by="dashboard")
                st.success("Mapping-Kandidat bestätigt; Audit-Log geschrieben.")
                st.write({"mapping_id": mapping_id})
            except Exception as exc:
                st.error(f"Mapping-Auswahl abgelehnt: {exc}")
        if st.button("Ausgewählten Mapping-Kandidaten ablehnen", key="mapping_candidate_reject"):
            try:
                if not selected_candidate_label:
                    raise ValueError("candidate is required")
                candidate_id = selected_candidate_label.split(" | ", 1)[0]
                reject_mapping_candidate(conn, candidate_id=candidate_id, note=mapping_note, created_by="dashboard")
                st.success("Mapping-Kandidat abgelehnt; Audit-Log geschrieben.")
            except Exception as exc:
                st.error(f"Mapping-Ablehnung abgelehnt: {exc}")
        st.caption("Manuell erfassen: neuen Katalogeintrag im Tab 'Instrument Search / Add Position' speichern. Keine Portfolio-Transaktion wird durch Mapping Review geändert.")
        st.divider()
        st.subheader("FX Override Review")
        st.caption("Historische FX-Rates nur manuell mit Quelle/Notiz erfassen. Keine Rates ohne bewusste Speicherung.")
        fx_rows = data.get_fx_override_requirements(conn)
        show_table(st, fx_rows)
        fx_labels = [f"{r['base_currency']}/{r['quote_currency']} {r['rate_date']} ({r['fx_status']})" for r in fx_rows]
        fx_label = st.selectbox("FX-Paar/Datum", fx_labels, key="fx_override_select") if fx_labels else None
        fx_rate = st.text_input("FX-Rate zu CHF", key="fx_override_rate")
        fx_note = st.text_area("FX-Quelle/Notiz (Pflicht)", key="fx_override_note")
        if st.button("Manual FX Override speichern", key="fx_override_save"):
            try:
                if not fx_label:
                    raise ValueError("fx pair/date is required")
                pair, date_part = fx_label.split(" ", 1)
                base, quote = pair.split("/", 1)
                rate_date = date_part.split(" ", 1)[0]
                set_manual_fx_override(conn, base_currency=base, quote_currency=quote, rate_date=rate_date, rate=Decimal(fx_rate), note=fx_note, created_by="dashboard")
                st.success("Manual FX Override gespeichert und auditierbar erfasst.")
            except Exception as exc:
                st.error(f"FX Override abgelehnt: {exc}")
