from __future__ import annotations

from decimal import Decimal

from jarvis_finance.dashboard.actions import action_button, edit_mode_enabled, read_only_hint
from jarvis_finance.dashboard.components.tables import show_table
from jarvis_finance.dashboard import data
from jarvis_finance.equity.manage import add_equity_transaction
from jarvis_finance.market_data.prices import equity_price_provider_by_name, refresh_market_prices
from jarvis_finance.services.portfolio_advisor import get_portfolio_advisor_snapshot
from jarvis_finance.dashboard.views.page_01_portfolio import _drilldown_rows, _import_rows, _lookthrough_rows, _plotly_donut

PAGE_TITLE = "Portfolio"


def _go_to_add_position(st) -> None:
    if hasattr(st, "session_state"):
        st.session_state["jarvis_requested_page"] = "Position hinzufügen"


def _equity_rows(conn) -> list[dict]:
    return [
        r for r in data.get_positions(conn)
        if str(r.get("asset_class", "")).lower() in {"stock", "equity", "etf"}
        and data.d(r.get("quantity")) != 0
        and str(r.get("instrument_status", "")).lower() != "inactive"
    ]


def _cash_rows(conn) -> list[dict]:
    rows: list[dict] = []
    for row in data.get_cash_overview(conn):
        if data.d(row.get("amount_original")) == 0:
            continue
        key = f"cash::{row.get('platform','')}::{row.get('account_name','')}::{row.get('currency','')}"
        rows.append({
            "_kind": "cash",
            "_key": key,
            "account_id": row.get("account_id", ""),
            "instrument_id": "",
            "platform": row.get("platform", ""),
            "account_name": row.get("account_name", ""),
            "asset_class": "cash",
            "name": row.get("account_name") or row.get("platform") or "Cash",
            "ticker": "",
            "isin": "",
            "quantity": row.get("amount_original", ""),
            "currency": row.get("currency", ""),
            "market_price_original": "1" if row.get("currency") == "CHF" else "",
            "price_currency": row.get("currency", ""),
            "price_date": "",
            "price_provider": "cash ledger",
            "market_value_chf": row.get("amount_chf", ""),
            "price_status": "ok" if row.get("amount_chf") else "missing_fx",
            "fx_status": "ok" if row.get("amount_chf") else "missing_fx",
            "cost_basis_chf": "",
            "quality_warnings": "" if row.get("amount_chf") else "missing_fx",
            "data_quality_status": row.get("quality_status", ""),
        })
    return rows


def _portfolio_rows(conn) -> list[dict]:
    rows: list[dict] = []
    for row in _cash_rows(conn):
        rows.append(row)
    for row in _equity_rows(conn):
        item = dict(row)
        item["_kind"] = "position"
        item["_key"] = f"position::{row.get('account_id')}::{row.get('instrument_id')}"
        rows.append(item)
    return rows


def _asset_type(row: dict) -> str:
    asset = str(row.get("asset_class", "")).lower()
    if asset == "cash":
        return "Cash"
    return "ETF" if asset == "etf" else "Aktie"


def _status(row: dict) -> str:
    if _asset_type(row) == "Cash":
        return "Bewertet" if row.get("market_value_chf") else "FX fehlt"
    warnings = set(str(row.get("quality_warnings") or "").split(","))
    if not row.get("market_price_original"):
        return "Preis fehlt"
    if "missing_fx" in warnings or row.get("fx_status") == "missing_fx" or (row.get("price_currency") not in {"", "CHF"} and not row.get("market_value_chf")):
        return "FX fehlt"
    if "cost_basis_uncertain" in warnings or not row.get("cost_basis_chf"):
        return "Einstand unvollständig"
    if row.get("market_value_chf"):
        return "Bewertet"
    return "Unvollständig"


def _market_value(row: dict) -> str:
    return data.chf_text(row.get("market_value_chf")) if row.get("market_value_chf") else "nicht vollständig bewertet"


def _price(row: dict) -> str:
    if _asset_type(row) == "Cash":
        return "1" if row.get("currency") == "CHF" else "FX offen"
    return row.get("market_price_original") or "Preis fehlt"


def _table_row(row: dict) -> dict[str, str]:
    return {
        "Name": row.get("name", ""),
        "Ticker": row.get("ticker", ""),
        "ISIN": row.get("isin", ""),
        "Depot": row.get("platform") or row.get("account_name", ""),
        "Assettyp": _asset_type(row),
        "Menge/Betrag": row.get("quantity", ""),
        "Währung": row.get("currency") or row.get("price_currency") or "",
        "Kurs": _price(row),
        "Marktwert CHF": _market_value(row),
        "Status": _status(row),
    }


def _sort_key(label: str):
    if label == "Marktwert CHF absteigend":
        return lambda r: (data.d(r.get("market_value_chf")), r.get("name", "").lower())
    if label == "Name A–Z":
        return lambda r: r.get("name", "").lower()
    if label == "Depot":
        return lambda r: (r.get("platform", ""), r.get("account_name", ""), r.get("name", ""))
    if label == "Assettyp":
        return lambda r: (_asset_type(r), r.get("name", ""))
    if label == "Status":
        return lambda r: (_status(r), r.get("name", ""))
    if label == "Währung":
        return lambda r: (r.get("price_currency") or r.get("currency") or "", r.get("name", ""))
    return lambda r: r.get("name", "").lower()


def _summary_numbers(rows: list[dict]) -> dict[str, Decimal | int | str]:
    cash = sum((data.d(r.get("market_value_chf")) for r in rows if _asset_type(r) == "Cash" and r.get("market_value_chf")), Decimal("0"))
    stocks = sum((data.d(r.get("market_value_chf")) for r in rows if _asset_type(r) == "Aktie" and r.get("market_value_chf")), Decimal("0"))
    etfs = sum((data.d(r.get("market_value_chf")) for r in rows if _asset_type(r) == "ETF" and r.get("market_value_chf")), Decimal("0"))
    missing_price = sum(1 for r in rows if _asset_type(r) != "Cash" and not r.get("market_price_original"))
    missing_fx = sum(1 for r in rows if _status(r) == "FX fehlt")
    incomplete_cost = sum(1 for r in rows if _asset_type(r) != "Cash" and ("cost_basis_uncertain" in str(r.get("quality_warnings") or "") or not r.get("cost_basis_chf")))
    latest_price_update = max((str(r.get("price_date") or "") for r in rows if _asset_type(r) != "Cash"), default="") or "kein Preisupdate"
    return {
        "cash": cash,
        "stocks": stocks,
        "etfs": etfs,
        "total": cash + stocks + etfs,
        "missing_price": missing_price,
        "missing_fx": missing_fx,
        "incomplete_cost": incomplete_cost,
        "latest_price_update": latest_price_update,
    }


def _num(nums: dict[str, Decimal | int | str], key: str) -> Decimal:
    return data.d(nums.get(key))


def _count(nums: dict[str, Decimal | int | str], key: str) -> int:
    return int(nums.get(key) or 0)


def _summary_cards(st, rows: list[dict]) -> None:
    n = _summary_numbers(rows)
    c1, c2, c3, c4 = st.columns(4)
    c1.metric("Gesamtwert CHF", data.chf_text(_num(n, "total")))
    c2.metric("Cash CHF", data.chf_text(_num(n, "cash")))
    c3.metric("Aktien CHF", data.chf_text(_num(n, "stocks")) if _count(n, "missing_price") == 0 and _count(n, "missing_fx") == 0 else f"{data.chf_text(_num(n, 'stocks'))} + offen")
    c4.metric("ETFs CHF", data.chf_text(_num(n, "etfs")) if _count(n, "missing_price") == 0 and _count(n, "missing_fx") == 0 else f"{data.chf_text(_num(n, 'etfs'))} + offen")
    c5, c6, c7, c8 = st.columns(4)
    c5.metric("Ohne Preis", str(_count(n, "missing_price")))
    c6.metric("Ohne FX", str(_count(n, "missing_fx")))
    c7.metric("Einstand offen", str(_count(n, "incomplete_cost")))
    c8.metric("Letztes Preisupdate", str(n["latest_price_update"]))


def _provider_summary(rows: list[dict]) -> list[dict[str, str]]:
    out: list[dict[str, str]] = []
    groups = sorted({r.get("platform") or r.get("account_name") or "Anderes Konto" for r in rows})
    for group in groups:
        group_rows = [r for r in rows if (r.get("platform") or r.get("account_name") or "Anderes Konto") == group]
        nums = _summary_numbers(group_rows)
        out.append({
            "Anbieter/Depot": group,
            "Cash CHF": data.chf_text(_num(nums, "cash")),
            "Aktien CHF": data.chf_text(_num(nums, "stocks")) if _count(nums, "missing_price") == 0 and _count(nums, "missing_fx") == 0 else "nicht vollständig bewertet",
            "ETFs CHF": data.chf_text(_num(nums, "etfs")) if _count(nums, "missing_price") == 0 and _count(nums, "missing_fx") == 0 else "nicht vollständig bewertet",
            "Gesamtwert Anbieter CHF": data.chf_text(_num(nums, "total")) if _count(nums, "missing_price") == 0 and _count(nums, "missing_fx") == 0 else f"{data.chf_text(_num(nums, 'total'))} + offen",
            "Positionen": str(len(group_rows)),
            "Unbewertet": str(_count(nums, "missing_price") + _count(nums, "missing_fx")),
        })
    return out


def _asset_summary(rows: list[dict]) -> list[dict[str, str]]:
    out: list[dict[str, str]] = []
    for label in ["Cash", "Aktie", "ETF"]:
        group_rows = [r for r in rows if _asset_type(r) == label]
        if not group_rows:
            continue
        nums = _summary_numbers(group_rows)
        value = _num(nums, "cash") if label == "Cash" else _num(nums, "stocks") if label == "Aktie" else _num(nums, "etfs")
        out.append({
            "Assettyp": "Aktien" if label == "Aktie" else "ETFs" if label == "ETF" else "Cash",
            "Gesamt CHF": data.chf_text(value) if _count(nums, "missing_price") == 0 and _count(nums, "missing_fx") == 0 else "nicht vollständig bewertet",
            "Anzahl": str(len(group_rows)),
            "Unbewertet": str(_count(nums, "missing_price") + _count(nums, "missing_fx")),
        })
    return out


def _selected_from_dataframe_event(event, rows: list[dict]) -> dict | None:
    try:
        selected_rows = list(event.selection.rows)  # streamlit >= 1.35
    except Exception:
        try:
            selected_rows = list(event["selection"]["rows"])
        except Exception:
            selected_rows = []
    if selected_rows:
        idx = int(selected_rows[0])
        if 0 <= idx < len(rows):
            return rows[idx]
    return None


def _render_open_buttons(st, rows: list[dict]) -> dict | None:
    st.caption("Fallback falls Zeilenauswahl im Browser nicht greift: Position direkt öffnen.")
    selected = None
    for idx, row in enumerate(rows):
        label = f"Öffnen: {row.get('name','Position')} · {_asset_type(row)} · {row.get('platform') or row.get('account_name','Depot')}"
        if st.button(label, key=f"portfolio_open_{idx}_{abs(hash(row.get('_key', idx)))}"):
            st.session_state["portfolio_selected_key"] = row.get("_key")
            selected = row
    return selected


def _price_update_message(stock, etf, rows_after: list[dict]) -> str:
    checked = stock.total_mappings + etf.total_mappings
    updated = stock.updated_count + etf.updated_count
    skipped = stock.skipped_count + etf.skipped_count + stock.cached_count + etf.cached_count
    providers = "auto: FMP → Twelve Data → Finnhub → Massive"
    still_missing = [r.get("name", "Unbekannte Position") for r in rows_after if _asset_type(r) != "Cash" and not r.get("market_price_original")]
    fx_missing = [r.get("name", "Unbekannte Position") for r in rows_after if _status(r) == "FX fehlt"]
    parts = [
        "Bewertung aktualisiert",
        f"Geprüft: {checked} Instrumente",
        f"Aktualisiert: {updated}",
        f"Übersprungen/Cache: {skipped}",
        f"Provider: {providers}",
        f"Weiterhin ohne Preis: {len(still_missing)}" + (f" ({', '.join(still_missing[:5])})" if still_missing else ""),
        f"FX fehlt: {len(fx_missing)}" + (f" ({', '.join(fx_missing[:5])})" if fx_missing else ""),
    ]
    warnings = stock.warning_count + etf.warning_count
    errors = stock.error_count + etf.error_count
    if warnings or errors:
        parts.append(f"Hinweise/Fehler: {warnings}/{errors}")
    return " · ".join(parts)


def _render_detail(st, conn, row: dict) -> None:
    st.subheader("Detailpanel")
    detail = {
        "Name": row.get("name", ""),
        "Assettyp": _asset_type(row),
        "Depot": row.get("platform") or row.get("account_name", ""),
        "Menge/Betrag": row.get("quantity", ""),
        "Währung": row.get("currency", ""),
        "Kurs": _price(row),
        "Marktwert CHF": _market_value(row),
        "Preisstatus": "Nicht nötig" if _asset_type(row) == "Cash" else ("Bewertet" if row.get("market_price_original") else "Preis fehlt"),
        "FX-Status": "Nicht nötig" if row.get("currency") == "CHF" else ("OK" if _status(row) != "FX fehlt" else "FX fehlt"),
        "Einstandsstatus": "Nicht nötig" if _asset_type(row) == "Cash" else ("Einstand unvollständig" if "cost_basis_uncertain" in str(row.get("quality_warnings") or "") or not row.get("cost_basis_chf") else "OK"),
        "Letzte Aktualisierung": row.get("price_date") or "kein Preisupdate",
        "Audit/Verlauf": "Verlauf über Button anzeigen",
    }
    st.write(detail)
    a, b, c, d, e, f = st.columns(6)
    row_key = str(row.get("_key", "selected")).replace(":", "_")
    if action_button(a, "Korrigieren", key=f"portfolio_action_correct_{row_key}", edit_required=True):
        st.session_state["portfolio_active_action"] = "Korrigieren"
    if b.button("Verlauf anzeigen", key=f"portfolio_action_history_{row_key}"):
        st.session_state["portfolio_active_action"] = "Verlauf"
    if _asset_type(row) == "Cash":
        c.button("Preis aktualisieren", key=f"portfolio_action_price_{row_key}", disabled=True, help="Cash braucht keinen Marktpreis; FX wird über Cash-/FX-Workflow gepflegt.")
    else:
        if c.button("Preis aktualisieren", key=f"portfolio_action_price_{row_key}"):
            stock = refresh_market_prices(conn, provider=equity_price_provider_by_name("auto"), asset_class="stock", only_isin=row.get("isin") or None, only_missing=False)
            etf = refresh_market_prices(conn, provider=equity_price_provider_by_name("auto"), asset_class="etf", only_isin=row.get("isin") or None, only_missing=False)
            st.success(_price_update_message(stock, etf, _portfolio_rows(conn)))
    d.button("Kaufen", key=f"portfolio_action_buy_{row_key}", disabled=True, help="MVP: bitte aktuell über Position hinzufügen als Kauf erfassen.")
    e.button("Verkaufen", key=f"portfolio_action_sell_{row_key}", disabled=True, help="MVP: Verkaufsworkflow noch nicht freigeschaltet.")
    f.button("Dividende/Ausschüttung", key=f"portfolio_action_dividend_{row_key}", disabled=True, help="MVP: Dividendenworkflow folgt später.")

    active = st.session_state.get("portfolio_active_action", "Details") if hasattr(st, "session_state") else "Details"
    if active == "Korrigieren":
        if _asset_type(row) == "Cash":
            st.info("Cash-Korrektur bitte über Position hinzufügen → Cash erfassen ausführen, damit sie auditiert wird.")
            return
        if not edit_mode_enabled(st):
            read_only_hint(st)
            return
        st.markdown("### Korrigieren — Inline-Formular")
        st.caption("Auditierbare manuelle Korrektur; keine Löschung, keine Admin-Suche nötig.")
        qty = st.text_input("Korrektur-Menge als Decimal", key=f"equity_correct_qty_{row_key}")
        gross = st.text_input("Korrektur-Betrag original", key=f"equity_correct_gross_{row_key}")
        note = st.text_area("Korrektur-Notiz Pflicht", key=f"equity_correct_note_{row_key}")
        confirm = st.checkbox("Ich bestätige die Korrektur.", key=f"equity_correct_confirm_{row_key}")
        if st.button("Korrektur speichern", key=f"equity_correct_save_{row_key}"):
            try:
                result = add_equity_transaction(conn, account_id=row["account_id"], instrument_id=row["instrument_id"], transaction_type="manual_adjustment", trade_date=str(row.get("price_date") or "2026-01-01"), quantity_text=qty, gross_amount_original_text=gross or "0", currency=row.get("currency") or "CHF", note=note, confirm=confirm, fx_status="not_needed" if (row.get("currency") or "CHF") == "CHF" else "missing")
                st.success(f"Korrektur auditiert: {result.transaction_id}")
            except Exception as exc:
                st.error(f"Korrektur abgelehnt: {exc}")
    elif active == "Verlauf":
        st.markdown("### Verlauf")
        if _asset_type(row) == "Cash":
            tx = data.rowdicts(conn.execute("SELECT trade_date, transaction_type, net_amount_original, currency_original, quality_status, notes FROM transactions WHERE account_id=? AND currency_original=? AND COALESCE(is_voided,0)=0 ORDER BY trade_date DESC, created_at DESC LIMIT 50", (row.get("account_id"), row.get("currency"))).fetchall())
            show_table(st, tx, empty_message="Kein Cash-Verlauf vorhanden.")
        else:
            tx = data.rowdicts(conn.execute("SELECT trade_date, transaction_type, quantity, currency_original, quality_status, notes FROM transactions WHERE account_id=? AND instrument_id=? AND COALESCE(is_voided,0)=0 ORDER BY trade_date DESC, created_at DESC", (row["account_id"], row["instrument_id"])).fetchall())
            audit = data.rowdicts(conn.execute("SELECT timestamp, source, action, user_text_note, created_by FROM audit_log WHERE entity_id IN (SELECT transaction_id FROM transactions WHERE account_id=? AND instrument_id=?) ORDER BY timestamp DESC LIMIT 50", (row["account_id"], row["instrument_id"])).fetchall())
            show_table(st, tx, empty_message="Kein Transaktionsverlauf vorhanden.")
            show_table(st, audit, empty_message="Kein Audit-Verlauf vorhanden.")
    else:
        st.info("Details werden oben im Detailpanel angezeigt.")


def _render_portfolio_advisor(st, conn) -> None:
    snapshot = get_portfolio_advisor_snapshot(conn)
    st.subheader("Portfolio Advisor / TrueWealth-Ersatz")
    st.caption("Echter Donut, klickbarer Drilldown, Lookthrough-Platzhalter und Import-Mapping für TrueWealth-Screens/PDFs. Preview-only, keine Auto-Orders.")
    left, right = st.columns(2)
    with left:
        _plotly_donut(st, "Quelle / Vermögensbereich", [item.model_dump() for item in snapshot.source_allocation])
    with right:
        _plotly_donut(st, "Asset Allocation", [item.model_dump() for item in snapshot.asset_allocation])

    selected_label = st.selectbox(
        "Donut-Segment für Drilldown",
        [item.label for item in snapshot.asset_allocation],
        key="portfolio_user_advisor_drilldown_segment",
    )
    selected_key = next(item.key for item in snapshot.asset_allocation if item.label == selected_label)
    show_table(st, _drilldown_rows(snapshot, selected_key))

    t1, t2, t3 = st.tabs(["Lookthrough", "TrueWealth Import", "Guardrails"])
    with t1:
        show_table(st, _lookthrough_rows(snapshot))
    with t2:
        show_table(st, _import_rows(snapshot))
        st.info("Nächster Schritt: lokale Parser/OCR-Mapping-Pipeline, Rohdateien bleiben außerhalb Git.")
    with t3:
        for guardrail in snapshot.guardrails:
            st.write(f"- {guardrail}")
        show_table(st, [card.model_dump() for card in snapshot.scorecards])


def render(st, conn) -> None:
    st.title("Portfolio")
    st.caption("Bedienbare User-Ansicht für Cash, Aktien und ETFs. Keine DOCX-/Review-Rohdaten, keine technischen IDs, keine API-Abfragen beim Laden.")
    rows = _portfolio_rows(conn)
    _summary_cards(st, rows)
    _render_portfolio_advisor(st, conn)

    if st.button("Bewertung aktualisieren", key="portfolio_refresh_valuation"):
        stock = refresh_market_prices(conn, provider=equity_price_provider_by_name("auto"), asset_class="stock", only_missing=True)
        etf = refresh_market_prices(conn, provider=equity_price_provider_by_name("auto"), asset_class="etf", only_missing=True)
        rows = _portfolio_rows(conn)
        st.success(_price_update_message(stock, etf, rows))

    if not rows:
        st.info("Noch keine Cash-, Aktien- oder ETF-Positionen im User Mode vorhanden.")
        if st.button("Position hinzufügen", key="portfolio_empty_add_position", on_click=_go_to_add_position, args=(st,)):
            st.success("Öffne im User Mode die Seite 'Position hinzufügen'.")
        return

    sort_by = st.selectbox("Sortierung", ["Marktwert CHF absteigend", "Name A–Z", "Depot", "Assettyp", "Status", "Währung"], key="portfolio_sort_by")
    rows = sorted(rows, key=_sort_key(sort_by), reverse=sort_by == "Marktwert CHF absteigend")
    view = st.radio("Ansicht", ["Nach Anbieter / Depot", "Nach Assettyp", "Alle Positionen"], key="portfolio_view")

    if view == "Nach Anbieter / Depot":
        preferred = ["PostFinance", "Raiffeisen", "True Wealth", "Anderes Konto"]
        seen: set[str] = set()
        ordered_groups = preferred + sorted({r.get("platform") or r.get("account_name") or "Anderes Konto" for r in rows if (r.get("platform") or r.get("account_name") or "Anderes Konto") not in preferred})
        for group in ordered_groups:
            if group in seen:
                continue
            seen.add(group)
            group_rows = [r for r in rows if group.lower() in ((r.get("platform", "") + " " + r.get("account_name", "")).lower())]
            if group_rows:
                st.markdown(f"### {group}")
                show_table(st, [_table_row(r) for r in group_rows])
        st.markdown("### Zusammenzug nach Anbieter / Depot")
        show_table(st, _provider_summary(rows))
    elif view == "Nach Assettyp":
        for label in ["Cash", "Aktie", "ETF"]:
            group_rows = [r for r in rows if _asset_type(r) == label]
            if group_rows:
                st.markdown(f"### {'Aktien' if label == 'Aktie' else 'ETFs' if label == 'ETF' else 'Cash'}")
                show_table(st, [_table_row(r) for r in group_rows])
        st.markdown("### Zusammenzug nach Assettyp")
        show_table(st, _asset_summary(rows))
    else:
        st.markdown("### Alle Positionen")
        show_table(st, [_table_row(r) for r in rows])

    st.subheader("Position aus Liste öffnen")
    display_rows = [_table_row(r) for r in rows]
    event = st.dataframe(display_rows, use_container_width=True, hide_index=True, on_select="rerun", selection_mode="single-row", key="portfolio_positions_dataframe")
    selected = _selected_from_dataframe_event(event, rows)
    if selected:
        st.session_state["portfolio_selected_key"] = selected.get("_key")
    selected = selected or _render_open_buttons(st, rows)
    selected_key = st.session_state.get("portfolio_selected_key") if hasattr(st, "session_state") else None
    if selected_key:
        selected = next((r for r in rows if r.get("_key") == selected_key), selected)
    selected = selected or rows[0]
    _render_detail(st, conn, selected)

    st.subheader("Weitere Aktion")
    if st.button("Position hinzufügen", key="portfolio_add_position", on_click=_go_to_add_position, args=(st,)):
        st.success("Öffne im User Mode die Seite 'Position hinzufügen'.")
