from __future__ import annotations

from jarvis_finance.dashboard import data as dashboard_data
from jarvis_finance.dashboard.views import page_03_equities_etfs, page_09_alerts
from jarvis_finance.dashboard.main import get_admin_navigation_titles, get_user_navigation_titles
from jarvis_finance.equity.cleanup import apply_review_test_equity_cleanup, identify_review_test_equity_artifacts
from jarvis_finance.equity.manage import add_manual_position_from_catalog, create_manual_catalog_entry, ensure_standard_broker_accounts
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations
from test_dashboard_mvp_regressions import setup_equity_conn
from test_dashboard_ux_sprint2 import FakeStreamlit, setup_multi_wallet_crypto_conn


class DuplicateKeyGuardStreamlit(FakeStreamlit):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.seen_button_keys: set[str] = set()

    def __getattr__(self, name: str):
        base = super().__getattr__(name)

        def recorder(*args, **kwargs):
            if name == "button":
                key = kwargs.get("key")
                if key:
                    assert str(key) not in self.seen_button_keys, f"duplicate Streamlit button key: {key}"
                    self.seen_button_keys.add(str(key))
            return base(*args, **kwargs)

        return recorder


def _visible_text(st: FakeStreamlit) -> str:
    parts: list[str] = []
    for _name, args, kwargs in st.calls:
        parts.extend(str(arg) for arg in args)
        parts.extend(str(value) for value in kwargs.values())
    return "\n".join(parts)


def test_user_navigation_is_short_and_admin_keeps_review_tools() -> None:
    user = get_user_navigation_titles()
    admin = get_admin_navigation_titles()

    assert user == ["Command Center", "Portfolio", "Crypto", "Wallets", "Position hinzufügen", "Reports"]
    assert "Manual Review Queue" not in user
    assert "Equity/ETF Manage" not in user
    assert "Manual Review Queue" in admin
    assert "Alerts" in admin


def test_alert_page_uses_unique_button_keys_and_hides_admin_table_in_user_mode() -> None:
    conn = connect_memory()
    apply_migrations(conn)
    for idx in range(2):
        conn.execute(
            """
            INSERT INTO alerts(alert_id, priority, status, category, entity_type, entity_id, rule_id, message, occurrence_count, last_seen_at, created_at)
            VALUES (?, 'warnung', 'active', 'test', 'instrument', ?, 'missing_market_price', 'synthetic message', 1, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')
            """,
            (f"alert-{idx}", f"instrument-{idx}"),
        )
    conn.commit()
    st = DuplicateKeyGuardStreamlit()

    page_09_alerts.render(st, conn)

    assert {"alert_alert-0_prüfen", "alert_alert-1_prüfen"} <= st.seen_button_keys
    assert "Admin: technische Alert-Tabelle anzeigen" not in _visible_text(st)


def test_review_docx_equity_cleanup_preserves_manual_dashboard_positions() -> None:
    conn, account_id, manual_catalog_id = setup_equity_conn()
    add_manual_position_from_catalog(
        conn,
        catalog_entry_id=manual_catalog_id,
        account_id=account_id,
        position_type="initial_snapshot",
        quantity_text="1",
        trade_date="2026-01-15",
        currency="USD",
        cost_basis_original_text="100",
        fx_status="missing",
        note="real manual dashboard position",
        confirm=True,
    )
    review_catalog_id = create_manual_catalog_entry(
        conn,
        asset_class="stock",
        name="DOCX Review Test AG",
        currency="CHF",
        isin="CH0000000009",
        ticker="DOCXT",
        exchange="SIX",
        provider="manual",
        provider_symbol="DOCXT",
        note="docx review synthetic artifact",
    )
    review_result = add_manual_position_from_catalog(
        conn,
        catalog_entry_id=review_catalog_id,
        account_id=account_id,
        position_type="initial_snapshot",
        quantity_text="5",
        trade_date="2026-01-15",
        currency="CHF",
        cost_basis_original_text="50",
        note="docx review synthetic artifact",
        confirm=True,
    )
    conn.execute(
        "UPDATE transactions SET source_type='broker_import_reviewed_snapshot', notes='docx review synthetic artifact' WHERE transaction_id=?",
        (review_result.transaction_id,),
    )
    conn.commit()

    plan = identify_review_test_equity_artifacts(conn)
    assert len(plan.transaction_ids) == 1
    assert plan.manual_position_count == 1
    audit_id = apply_review_test_equity_cleanup(conn, plan=plan, note="test cleanup")

    assert audit_id
    active_manual = conn.execute("SELECT COUNT(*) AS c FROM transactions WHERE source_type='manual_dashboard' AND COALESCE(is_voided,0)=0").fetchone()["c"]
    voided_review = conn.execute("SELECT COUNT(*) AS c FROM transactions WHERE source_type='broker_import_reviewed_snapshot' AND COALESCE(is_voided,0)=1").fetchone()["c"]
    assert active_manual == 1
    assert voided_review == 1


def test_equity_page_renders_user_summary_selection_actions_and_no_raw_ids() -> None:
    conn, account_id, catalog_id = setup_equity_conn()
    add_manual_position_from_catalog(
        conn,
        catalog_entry_id=catalog_id,
        account_id=account_id,
        position_type="initial_snapshot",
        quantity_text="2",
        trade_date="2026-01-15",
        currency="USD",
        cost_basis_original_text="100",
        fx_status="missing",
        note="synthetic equity interaction",
        confirm=True,
    )
    st = FakeStreamlit(inputs={"portfolio_view": "Alle Positionen", "portfolio_sort_by": "Name A–Z"})

    page_03_equities_etfs.render(st, conn)

    text = _visible_text(st)
    for expected in ["Gesamtwert CHF", "Position aus Liste öffnen", "Detailpanel", "Korrigieren", "Verlauf anzeigen", "Position hinzufügen"]:
        assert expected in text
    assert "instrument_id" not in text
    assert "account_id" not in text


def test_crypto_detail_contains_minimal_coingecko_link_without_raw_ids() -> 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["coingecko_link"] == "https://www.coingecko.com/en/coins/ethereum"
    assert "coingecko_id" not in detail
    assert "asset_id" not in detail

    from jarvis_finance.dashboard.views import page_04_crypto

    st = FakeStreamlit()
    page_04_crypto.render(st, conn)
    text = _visible_text(st)
    assert "CoinGecko öffnen" in text
    assert "https://www.coingecko.com/en/coins/ethereum" in text


def test_equity_page_does_not_refresh_prices_on_plain_render(monkeypatch) -> None:
    conn, account_id, catalog_id = setup_equity_conn()
    add_manual_position_from_catalog(
        conn,
        catalog_entry_id=catalog_id,
        account_id=account_id,
        position_type="initial_snapshot",
        quantity_text="1",
        trade_date="2026-01-15",
        currency="USD",
        cost_basis_original_text="100",
        fx_status="missing",
        note="plain render should not call provider",
        confirm=True,
    )

    def fail_provider(*_args, **_kwargs):
        raise AssertionError("provider must not be called during normal page render")

    monkeypatch.setattr(page_03_equities_etfs, "equity_price_provider_by_name", fail_provider)
    page_03_equities_etfs.render(FakeStreamlit(), conn)
