from __future__ import annotations

from decimal import Decimal

from jarvis_finance.crypto.assets import create_crypto_asset
from jarvis_finance.crypto.holdings import create_initial_holding_snapshot
from jarvis_finance.crypto.wallets import create_wallet
from jarvis_finance.dashboard import data as dashboard_data
from jarvis_finance.dashboard.demo_data import load_synthetic_demo_data
from jarvis_finance.dashboard.pages import page_12_crypto_manage
from jarvis_finance.market.providers import PriceQuote, store_crypto_price
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


class FakeStreamlit:
    def __init__(self, button_presses: set[str] | None = None, *, inputs: dict[str, object] | None = None, checkboxes: set[str] | None = None) -> None:
        self.calls: list[tuple[str, tuple[object, ...], dict[str, object]]] = []
        self.sidebar = self
        self.session_state = {}
        self.button_presses = button_presses or set()
        self.inputs = inputs or {}
        self.checkboxes = checkboxes or set()

    def __getattr__(self, name: str):
        def recorder(*args, **kwargs):
            self.calls.append((name, args, kwargs))
            if name == "button":
                label = str(args[0]) if args else str(kwargs.get("label", ""))
                pressed = label in self.button_presses and not bool(kwargs.get("disabled", False))
                if pressed and callable(kwargs.get("on_click")):
                    kwargs["on_click"](*kwargs.get("args", ()), **kwargs.get("kwargs", {}))
                return pressed
            if name == "checkbox":
                label = str(args[0]) if args else str(kwargs.get("label", ""))
                key = str(kwargs.get("key", label))
                return bool(self.inputs.get(key, label in self.checkboxes))
            if name == "toggle":
                return bool(kwargs.get("value", False))
            if name in {"text_input", "text_area", "date_input"}:
                key = str(kwargs.get("key", args[0] if args else ""))
                if key in self.inputs:
                    return self.inputs[key]
                return ""
            if name in {"selectbox", "radio", "pills"}:
                options = args[1] if len(args) > 1 else kwargs.get("options", [])
                key = str(kwargs.get("key", args[0] if args else ""))
                if key in self.inputs:
                    return self.inputs[key]
                return list(options)[0] if options else None
            if name in {"tabs", "columns"}:
                values = args[0] if args else []
                count = values if isinstance(values, int) else len(values)
                return [self for _ in range(count)]
            if name == "expander":
                return self
            return None
        return recorder

    def __enter__(self):
        return self

    def __exit__(self, *args):
        return False


def setup_demo_conn():
    conn = connect_memory()
    apply_migrations(conn)
    load_synthetic_demo_data(conn)
    return conn


def setup_multi_wallet_crypto_conn():
    conn = connect_memory()
    apply_migrations(conn)
    w1 = create_wallet(conn, wallet_name="MetaMask Demo", wallet_type="Software Wallet", platform_provider="MetaMask", last_verified_at="2026-01-31T10:00:00Z")
    w2 = create_wallet(conn, wallet_name="SwissBorg Demo", wallet_type="Exchange", platform_provider="SwissBorg", last_verified_at="2026-01-31T11:00:00Z")
    eth = create_crypto_asset(conn, coin_name="Ethereum", symbol="ETH", coingecko_id="ethereum")
    btc = create_crypto_asset(conn, coin_name="Bitcoin", symbol="BTC", coingecko_id="bitcoin")
    create_initial_holding_snapshot(conn, asset_id=eth, wallet_id=w1, quantity=Decimal("2"), verification_status="verified", last_verified_at="2026-01-31T10:00:00Z", note="synthetic eth split")
    create_initial_holding_snapshot(conn, asset_id=eth, wallet_id=w2, quantity=Decimal("2"), verification_status="verified", last_verified_at="2026-01-31T11:00:00Z", note="synthetic eth split")
    create_initial_holding_snapshot(conn, asset_id=btc, wallet_id=w1, quantity=Decimal("0.00000001"), verification_status="stale", last_verified_at="2026-01-01T00:00:00Z", note="synthetic btc tiny")
    store_crypto_price(conn, asset_id=eth, quote=PriceQuote("ethereum", "CHF", Decimal("2500.00"), provider="synthetic-cache", provider_timestamp="2026-02-01T00:00:00Z"))
    store_crypto_price(conn, asset_id=btc, quote=PriceQuote("bitcoin", "CHF", Decimal("40000.00"), provider="synthetic-cache", provider_timestamp="2026-02-01T00:00:00Z"))
    return conn

TECHNICAL_FIELDS = {
    "asset_id",
    "wallet_id",
    "instrument_id",
    "account_id",
    "entity_id",
    "rule_id",
    "source_id",
    "provider_symbol_status",
    "hedge_status",
    "dedup_key",
    "fingerprint",
}


def test_user_mode_crypto_and_portfolio_tables_are_clean_and_formatted() -> None:
    conn = setup_multi_wallet_crypto_conn()
    crypto_rows = dashboard_data.get_crypto_coin_summary(conn)
    assert crypto_rows
    visible_crypto = dashboard_data.visible_user_columns(crypto_rows[0])
    assert len(visible_crypto) <= 8
    assert TECHNICAL_FIELDS.isdisjoint(visible_crypto)
    assert crypto_rows[0]["kurs_chf"].startswith("CHF ")
    assert "'" in dashboard_data.chf_text(Decimal("12345.67"))
    assert dashboard_data.translate_status("missing_market_price") == "Preis fehlt"
    assert dashboard_data.translate_status("fresh") == "Aktuell"

    portfolio = dashboard_data.get_portfolio_positions_user(conn)
    assert portfolio == [] or TECHNICAL_FIELDS.isdisjoint(portfolio[0])
    assert all(len(row) <= 8 for row in portfolio)


def test_chart_data_for_crypto_coin_and_wallet_distribution_is_correct() -> None:
    conn = setup_multi_wallet_crypto_conn()
    coin_chart = dashboard_data.get_crypto_coin_allocation_chart(conn)
    wallet_chart = dashboard_data.get_crypto_wallet_allocation_chart(conn)
    assert [row["label"] for row in coin_chart][:2] == ["Ethereum", "Bitcoin"]
    assert coin_chart[0]["value_chf"] == "10000.00"
    assert {row["label"] for row in wallet_chart} == {"MetaMask Demo", "SwissBorg Demo"}
    assert all("asset_id" not in row and "wallet_id" not in row for row in coin_chart + wallet_chart)


def test_coin_detail_and_wallet_detail_are_selectable_read_models() -> None:
    conn = setup_multi_wallet_crypto_conn()
    eth = next(row for row in dashboard_data.get_crypto_coin_summary(conn) if row["symbol"] == "ETH")
    detail = dashboard_data.get_crypto_coin_detail(conn, eth["_asset_id"], price_max_age_seconds=999999999)
    assert detail["coin"] == "Ethereum"
    assert detail["gesamtwert_chf"] == "CHF 10'000.00"
    assert detail["preisstatus"] == "Aktuell"
    assert len(detail["wallets"]) == 2
    assert {"Bestand hinzufügen", "Bestand korrigieren", "Transfer", "Bestand auf 0 setzen", "Audit / Verlauf anzeigen"} <= set(detail["actions"])
    assert TECHNICAL_FIELDS.isdisjoint(detail)
    assert TECHNICAL_FIELDS.isdisjoint(detail["wallets"][0])

    wallet = dashboard_data.get_wallet_user_overview(conn)[0]
    wallet_detail = dashboard_data.get_wallet_detail(conn, wallet["_wallet_id"])
    assert wallet_detail["wallet_name"]
    assert wallet_detail["coins"]
    assert {"Coin in Wallet hinzufügen", "Bestand korrigieren", "Transfer aus Wallet", "Wallet verifizieren", "Wallet bearbeiten", "Wallet deaktivieren"} <= set(wallet_detail["actions"])
    assert TECHNICAL_FIELDS.isdisjoint(wallet_detail)


def test_alert_cards_are_readable_and_do_not_expose_rule_ids() -> None:
    conn = setup_demo_conn()
    cards = dashboard_data.get_alert_cards(conn, limit=5)
    summary = dashboard_data.get_alert_summary_cards(conn)
    assert {"kritisch", "wichtig", "info"} <= set(summary)
    assert cards
    assert {"title", "description", "affected", "severity", "actions"} <= set(cards[0])
    assert TECHNICAL_FIELDS.isdisjoint(cards[0])
    assert not cards[0]["title"].startswith("missing_")


def test_crypto_manage_renders_action_cards_not_all_forms_by_default() -> None:
    conn = setup_multi_wallet_crypto_conn()
    st = FakeStreamlit()
    page_12_crypto_manage.render(st, conn)
    buttons = [args[0] for name, args, _kwargs in st.calls if name == "button" and args]
    text_inputs = [args[0] for name, args, _kwargs in st.calls if name == "text_input" and args]
    assert {"Coin hinzufügen", "Bestand korrigieren", "Transfer zwischen Wallets", "Bestand entfernen / auf 0 setzen", "Neues Wallet", "Verlauf ansehen"} <= set(buttons)
    assert len(text_inputs) <= 3


def test_admin_mode_can_still_access_technical_fields() -> None:
    conn = setup_multi_wallet_crypto_conn()
    assert "asset_id" in dashboard_data.get_crypto_coin_summary(conn, admin_mode=True)[0]
    assert "wallet_id" in dashboard_data.get_wallet_user_overview(conn, admin_mode=True)[0]


def _call_args(st: FakeStreamlit, name: str) -> list[tuple[object, ...]]:
    return [args for call_name, args, _kwargs in st.calls if call_name == name]


def test_user_mode_portfolio_page_groups_cash_equities_etfs_without_ids() -> None:
    from jarvis_finance.dashboard.pages import page_03_equities_etfs

    conn = setup_demo_conn()
    st = FakeStreamlit()
    page_03_equities_etfs.render(st, conn)

    texts = "\n".join(str(arg) for _name, args, _kwargs in st.calls for arg in args)
    assert "Gesamtwert CHF" in texts
    assert "Cash CHF" in texts
    assert "Ohne Preis" in texts
    assert "Position aus Liste öffnen" in texts
    assert "Detailpanel" in texts
    assert "Position hinzufügen" in texts

    table_payloads = [args[0] for args in _call_args(st, "dataframe") if args and isinstance(args[0], list)]
    assert table_payloads
    flattened_rows = [row for payload in table_payloads for row in payload if isinstance(row, dict)]
    assert flattened_rows
    visible_keys = {key for row in flattened_rows for key in row}
    assert TECHNICAL_FIELDS.isdisjoint(visible_keys)
    assert {"Assettyp", "Marktwert CHF", "Status"} <= visible_keys

    buttons = [args[0] for args in _call_args(st, "button") if args]
    assert "Bewertung aktualisieren" in buttons
    assert "Position hinzufügen" in buttons
    assert any(str(label).startswith("Öffnen:") for label in buttons)


def test_command_center_summary_has_separate_asset_totals_and_unvalued_counts() -> None:
    conn = setup_demo_conn()
    summary = dashboard_data.get_command_center_summary(conn)
    assert {
        "cash_total_chf",
        "crypto_total_chf",
        "stock_total_chf",
        "etf_total_chf",
        "unvalued_position_count",
        "missing_price_count",
        "missing_fx_count",
        "equity_price_data_as_of",
    } <= set(summary)
    assert dashboard_data.d(summary["cash_total_chf"]) >= 0
    assert dashboard_data.d(summary["crypto_total_chf"]) >= 0
    assert int(summary["missing_price_count"]) >= 0


def test_command_center_renders_clear_price_update_labels_without_internal_ids() -> None:
    from jarvis_finance.dashboard.pages import page_00_command_center

    conn = setup_demo_conn()
    st = FakeStreamlit()
    page_00_command_center.render(st, conn)

    texts = "\n".join(str(arg) for _name, args, _kwargs in st.calls for arg in args)
    assert "Gesamtportfolio CHF" in texts
    assert "Aktien CHF" in texts
    assert "ETFs CHF" in texts
    assert "Unbewertete Positionen" in texts
    assert "Top 5 Crypto-Positionen" in texts
    assert "asset_id" not in texts
    assert "instrument_id" not in texts


def test_user_mode_navigation_uses_new_portfolio_page_and_admin_keeps_overview() -> None:
    from jarvis_finance.dashboard import main

    assert "Portfolio" in main.USER_PAGE_TITLES
    assert "Portfolio Übersicht" in main.ADMIN_PAGE_TITLES
    assert "Portfolio Übersicht" not in main.USER_PAGE_TITLES
    assert main.TITLE_ALIASES["Equities/ETFs"] == "Portfolio"


def test_reports_user_mode_hides_runtime_file_paths() -> None:
    from jarvis_finance.dashboard.pages import page_08_reports

    conn = setup_demo_conn()
    conn.execute(
        """
        INSERT INTO reports(report_id, report_type, title, file_path, format, generated_at, data_quality_status, created_at)
        VALUES ('report-demo', 'crypto_inventory', 'Demo Report', '/home/agent/jarvis_runtime/finance-system/reports/demo.html', 'html', '2026-02-01T00:00:00Z', 'ok', '2026-02-01T00:00:00Z')
        """
    )
    conn.commit()

    st = FakeStreamlit()
    page_08_reports.render(st, conn)

    texts = "\n".join(str(arg) for _name, args, _kwargs in st.calls for arg in args)
    assert "Runtime-Reports-Ordner" in texts
    assert "file_path" not in texts
    assert "/home/agent/jarvis_runtime" not in texts

    table_payloads = [args[0] for args in _call_args(st, "dataframe") if args and isinstance(args[0], list)]
    assert table_payloads
    assert "file_path" not in {key for row in table_payloads[0] for key in row}
