from __future__ import annotations

from decimal import Decimal
from types import SimpleNamespace

from jarvis_finance.api.routers.overview import wealth_cockpit as wealth_cockpit_endpoint
from jarvis_finance.api.schemas.positions import CashSnapshotConfirmRequest
from jarvis_finance.api.schemas.wealth_cockpit import WealthCockpitResponse
from jarvis_finance.services import wealth_cockpit as cockpit
from jarvis_finance.services.cash_service import confirm_cash_snapshot
from jarvis_finance.services.performance_scope import (
    set_performance_cashflow_coverage,
    set_performance_scope_classification,
)
from jarvis_finance.services.portfolio_performance import build_portfolio_performance
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

NOW = "2026-08-01T12:00:00+00:00"


def base_db():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,created_at) VALUES('bank','Bank','bank',?)",
        (NOW,),
    )
    conn.execute(
        "INSERT INTO platforms(platform_id,name,platform_type,created_at) VALUES('broker','Broker','broker',?)",
        (NOW,),
    )
    conn.execute(
        """INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at)
           VALUES('bank-a','bank','Household bank','cash','CHF',0,1,?)""",
        (NOW,),
    )
    conn.execute(
        """INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,performance_included,is_active,created_at)
           VALUES('depot','broker','Investment account','brokerage','CHF',0,1,?)""",
        (NOW,),
    )
    return conn


def add_activity(conn, tx_id: str, account_id: str, kind: str, amount: str, *, group: str | None = None, at: str = "2026-03-01"):
    conn.execute(
        """INSERT INTO transactions(
             transaction_id,transaction_type,activity_kind,account_id,trade_date,booking_date,
             event_timestamp,net_amount_original,currency_original,fx_rate_to_chf,source_type,
             source_id,row_hash,is_confirmed,is_voided,quality_status,created_at,internal_transfer_group_id)
           VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,1,0,'ok',?,?)""",
        (tx_id, kind, kind, account_id, at, at, at, amount, "CHF", "1", "synthetic_import", "fixture", f"hash-{tx_id}", NOW, group),
    )


def add_valuation(
    conn,
    snapshot_id: str,
    at: str,
    value: str,
    *,
    currency: str = "CHF",
    fx: str | None = "1",
):
    conn.execute(
        """INSERT INTO portfolio_valuation_snapshots(
             snapshot_id,scope_kind,scope_id,account_id,value_original,currency,base_currency,
             fx_rate_to_base,fx_direction,valuation_at,source,captured_at,snapshot_version,
             source_reference,quality_status,reason_codes_json)
           VALUES(?,?,?,?,?,?,'CHF',?,'original_to_base',?,'synthetic',?,1,?,'complete','[]')""",
        (
            snapshot_id,
            "account",
            "depot",
            "depot",
            value,
            currency,
            fx,
            at,
            NOW,
            f"ref-{snapshot_id}",
        ),
    )


def test_scope_boundary_keeps_household_transfer_neutral_but_counts_portfolio_deposit_and_ignores_trades():
    conn = base_db()
    add_activity(conn, "bank-leg", "bank-a", "internal_transfer", "-1000", group="pair-1")
    add_activity(conn, "depot-leg", "depot", "internal_transfer", "1000", group="pair-1")
    add_activity(conn, "buy", "depot", "buy", "-700", at="2026-03-02")
    add_activity(conn, "sell", "depot", "sell", "300", at="2026-03-03")
    add_activity(conn, "dividend", "depot", "dividend", "50", at="2026-03-04")

    household = cockpit.scope_cashflows(
        conn,
        account_ids=["bank-a", "depot"],
        from_date="2026-01-01",
        to_date="2026-12-31",
        data_cutoff=NOW,
    )
    investments = cockpit.scope_cashflows(
        conn,
        account_ids=["depot"],
        from_date="2026-01-01",
        to_date="2026-12-31",
        data_cutoff=NOW,
    )

    assert household == []
    assert investments == [
        {"at": "2026-03-01", "kind": "external_deposit", "amount_chf": "1000.00"}
    ]


def test_canonical_performance_engine_adjusts_investment_result_for_net_deposit_and_is_deterministic():
    conn = base_db()
    set_performance_scope_classification(
        conn,
        account_id="depot",
        included=True,
        classification_role="crypto_portfolio",
        source="test",
        note="synthetic complete portfolio",
        classified_at=NOW,
    )
    set_performance_cashflow_coverage(
        conn,
        account_id="depot",
        coverage_from="2026-01-01",
        coverage_to="2026-12-31",
        status="complete",
        source="test",
        note="synthetic complete history",
        recorded_at=NOW,
    )
    add_valuation(conn, "open", "2026-01-01", "10000")
    add_valuation(conn, "boundary", "2026-03-01", "11000")
    add_valuation(conn, "close", "2026-12-31", "13000")
    add_activity(conn, "deposit", "depot", "external_deposit", "1000", at="2026-03-01")

    first = build_portfolio_performance(
        conn, from_date="2026-01-01", to_date="2026-12-31", method="twr", data_cutoff=NOW
    )
    second = build_portfolio_performance(
        conn, from_date="2026-01-01", to_date="2026-12-31", method="twr", data_cutoff=NOW
    )

    assert first == second
    assert first["summary"]["net_external_cashflows"] == "1000"
    assert first["summary"]["investment_result"] == "2000"
    assert first["summary"]["ttwror_cumulative"] is not None


def test_missing_opening_value_never_creates_false_return():
    conn = base_db()
    set_performance_scope_classification(
        conn,
        account_id="depot",
        included=True,
        classification_role="crypto_portfolio",
        source="test",
        note="synthetic incomplete portfolio",
        classified_at=NOW,
    )
    set_performance_cashflow_coverage(
        conn,
        account_id="depot",
        coverage_from="2026-01-01",
        coverage_to="2026-12-31",
        status="complete",
        source="test",
        note="synthetic complete history",
        recorded_at=NOW,
    )
    add_valuation(conn, "close", "2026-12-31", "13000")
    result = build_portfolio_performance(
        conn, from_date="2026-01-01", to_date="2026-12-31", method="twr", data_cutoff=NOW
    )
    assert result["summary"]["ttwror_cumulative"] is None
    assert result["summary"]["investment_result"] is None
    assert "missing_opening_valuation" in result["quality"]["ttwror"]["reason_codes"]


def test_fx_uses_stored_canonical_rate_at_each_stichtag_and_missing_fx_fails_closed():
    conn = base_db()
    set_performance_scope_classification(
        conn,
        account_id="depot",
        included=True,
        classification_role="crypto_portfolio",
        source="test",
        note="synthetic FX portfolio",
        classified_at=NOW,
    )
    set_performance_cashflow_coverage(
        conn,
        account_id="depot",
        coverage_from="2026-01-01",
        coverage_to="2026-12-31",
        status="complete",
        source="test",
        note="synthetic complete history",
        recorded_at=NOW,
    )
    add_valuation(conn, "eur-open", "2026-01-01", "10000", currency="EUR", fx="0.9")
    add_valuation(conn, "eur-close", "2026-12-31", "11000", currency="EUR", fx="1.0")
    result = build_portfolio_performance(
        conn, from_date="2026-01-01", to_date="2026-12-31", method="twr", data_cutoff=NOW
    )
    assert result["summary"]["opening_value"] == "9000.0"
    assert result["summary"]["closing_value"] == "11000.0"

    missing = base_db()
    set_performance_scope_classification(
        missing,
        account_id="depot",
        included=True,
        classification_role="crypto_portfolio",
        source="test",
        note="synthetic missing FX portfolio",
        classified_at=NOW,
    )
    set_performance_cashflow_coverage(
        missing,
        account_id="depot",
        coverage_from="2026-01-01",
        coverage_to="2026-12-31",
        status="complete",
        source="test",
        note="synthetic complete history",
        recorded_at=NOW,
    )
    add_valuation(missing, "missing-open", "2026-01-01", "10000", currency="EUR", fx=None)
    add_valuation(missing, "missing-close", "2026-12-31", "11000", currency="EUR", fx=None)
    unavailable = build_portfolio_performance(
        missing, from_date="2026-01-01", to_date="2026-12-31", method="twr", data_cutoff=NOW
    )
    assert unavailable["summary"]["ttwror_cumulative"] is None
    assert "missing_fx" in unavailable["quality"]["ttwror"]["reason_codes"]


def test_current_distribution_counts_truewealth_once_keeps_unassigned_visible_and_reconciles(monkeypatch):
    cash_position = SimpleNamespace(
        platform="Raiffeisen",
        amount_chf="100.00",
        last_manual_reconciliation="2026-07-31",
        last_imported_booking=None,
        status="Aktuell",
    )
    monkeypatch.setattr(cockpit, "get_cash_summary", lambda conn: SimpleNamespace(cash_chf="100.00", positions=[cash_position]))
    monkeypatch.setattr(
        cockpit,
        "get_equity_summary",
        lambda conn: SimpleNamespace(valued_partial_chf="200.00", coverage_complete=True, unvalued_positions=0, as_of="2026-07-31"),
    )
    monkeypatch.setattr(
        cockpit,
        "list_crypto_positions",
        lambda conn: [SimpleNamespace(market_value_chf="50.00", last_price_update="2026-08-01T12:00:00+00:00")],
    )
    monkeypatch.setattr(cockpit, "_truewealth", lambda conn: (Decimal("300"), "2026-07-31"))
    monkeypatch.setattr(
        cockpit,
        "_unassigned_values",
        lambda conn: (Decimal("25"), [{"label": "Bestätigter weiterer Wert", "value_chf": "25.00", "as_of": "2026-07-31"}]),
    )

    current = cockpit._current_values(base_db(), as_of=cockpit.date(2026, 8, 1))

    assert current["total"] == Decimal("675.00")
    assert current["investments"] == Decimal("575.00")
    assert sum(Decimal(row["value_chf"]) for row in current["distribution"]) == current["total"]
    assert [row["key"] for row in current["distribution"]].count("truewealth") == 1
    assert any(row["key"] == "other" and row["value_chf"] == "25.00" for row in current["distribution"])
    assert next(row for row in current["sources"] if row["key"] == "crypto")["freshness_status"] == "fresh"
    assert all(
        row["reconciliation_status"] == "not_assessable"
        for row in current["sources"]
        if row["key"] in {"postfinance-investments", "truewealth", "crypto"}
    )


def test_cockpit_keeps_planning_separate_and_quality_dimensions_independent(monkeypatch):
    current = {
        "total": Decimal("1000"),
        "investments": Decimal("700"),
        "cash": Decimal("300"),
        "complete": True,
        "distribution": [
            {"key": "cash", "label": "Bankguthaben", "value_chf": "300.00"},
            {"key": "equity", "label": "Aktien und ETFs", "value_chf": "700.00"},
            {"key": "truewealth", "label": "True Wealth", "value_chf": "0.00"},
            {"key": "crypto", "label": "Kryptowährungen", "value_chf": "0.00"},
        ],
        "sources": [],
        "data_as_of": "2026-07-31",
        "unpriced_count": 0,
        "missing_cash_count": 0,
        "unassigned_items": [],
    }
    perf = {
        "summary": {
            "investment_result": "50",
            "net_external_cashflows": "100",
            "ttwror_cumulative": "0.05",
        },
        "quality": {"ttwror": {"status": "complete", "reason_codes": []}},
        "external_cashflows": [{"at": "2026-03-01", "kind": "external_deposit", "amount": "100"}],
    }
    monkeypatch.setattr(cockpit, "_current_values", lambda conn, as_of: current)
    monkeypatch.setattr(cockpit, "_latest_data_cutoff", lambda conn: NOW)
    monkeypatch.setattr(cockpit, "_latest_valuation_date", lambda conn, fallback: fallback)
    monkeypatch.setattr(cockpit, "build_portfolio_performance", lambda *args, **kwargs: perf)
    monkeypatch.setattr(cockpit, "scope_cashflows", lambda *args, **kwargs: [])
    monkeypatch.setattr(
        cockpit,
        "build_reconciliation_snapshot",
        lambda conn, now: {
            "data_quality_status": "stale",
            "reconciliations": [{"status": "difference"}],
            "snapshots": [],
        },
    )
    monkeypatch.setattr(cockpit, "_policy_comparison", lambda conn, current: {"configured": False, "version": None, "rows": [], "contribution": None})
    monkeypatch.setattr(
        cockpit,
        "get_annual_budget_assistant",
        lambda *args, **kwargs: {"summary_kpis": [{"key": "free_after_special", "value_chf": "500.00"}]},
    )

    first = cockpit.build_wealth_cockpit(base_db(), period="ytd", as_of="2026-08-01", data_cutoff=NOW)
    second = cockpit.build_wealth_cockpit(base_db(), period="ytd", as_of="2026-08-01", data_cutoff=NOW)

    assert first == second
    assert first["totals"]["captured_wealth_chf"] == "1000.00"
    assert first["planning"] == {
        "free_plannable_chf": "500.00",
        "available": True,
        "link": "/planning/budget/planning",
        "included_in_wealth": False,
    }
    assert first["data_quality"]["freshness_status"] == "unavailable"
    assert first["data_quality"]["reconciliation_status"] == "difference"
    assert first["data_quality"]["performance_status"] == "complete"
    assert len(first["kpis"]) == 6
    assert len(first["hints"]) <= 3


def test_existing_versioned_policy_supports_contribution_orientation_without_schema_change(monkeypatch):
    monkeypatch.setattr(
        cockpit,
        "active_policy",
        lambda conn: {
            "configured": True,
            "policy": {
                "version": 3,
                "monthly_contribution": "500.00",
                "allocations": [
                    {"asset_class": "cash", "target_pct": "30", "lower_pct": "20", "upper_pct": "40"},
                    {"asset_class": "equity", "target_pct": "70", "lower_pct": "60", "upper_pct": "80"},
                ],
            },
        },
    )
    current = {
        "complete": True,
        "total": Decimal("1000"),
        "distribution": [
            {"key": "cash", "value_chf": "300.00"},
            {"key": "equity", "value_chf": "700.00"},
            {"key": "truewealth", "value_chf": "0.00"},
            {"key": "crypto", "value_chf": "0.00"},
        ],
    }
    policy = cockpit._policy_comparison(base_db(), current)
    assert policy["version"] == 3
    assert policy["contribution"] == {
        "monthly_target_chf": "500.00",
        "annual_target_chf": "6000.00",
    }
    assert all(row["status"] == "within_range" for row in policy["rows"])


def test_period_boundaries_cover_year_change_and_last_twelve_months():
    conn = base_db()
    assert cockpit.period_bounds(conn, period="ytd", as_of=cockpit.date(2026, 8, 1)) == (
        cockpit.date(2026, 1, 1),
        cockpit.date(2026, 8, 1),
    )
    assert cockpit.period_bounds(conn, period="previous_year", as_of=cockpit.date(2026, 8, 1)) == (
        cockpit.date(2025, 1, 1),
        cockpit.date(2025, 12, 31),
    )
    assert cockpit.period_bounds(conn, period="12m", as_of=cockpit.date(2026, 8, 1)) == (
        cockpit.date(2025, 8, 1),
        cockpit.date(2026, 8, 1),
    )


def test_cash_account_without_snapshot_or_import_is_missing_not_zero():
    conn = base_db()
    current = cockpit._current_values(conn, as_of=cockpit.date(2026, 8, 1))
    cash_row = next(row for row in current["distribution"] if row["key"] == "cash")
    bank_source = next(row for row in current["sources"] if row["kind"] == "Bankguthaben")
    assert cash_row["value_chf"] is None
    assert next(row for row in current["distribution"] if row["key"] == "equity")[
        "value_chf"
    ] is None
    assert next(row for row in current["distribution"] if row["key"] == "crypto")[
        "value_chf"
    ] is None
    assert bank_source["current_value_chf"] is None
    assert bank_source["current_value_status"] == "not_ready"
    assert bank_source["reconciliation_status"] == "not_assessable"
    assert all(source["label"] != "Investment account" for source in current["sources"])
    assert current["missing_cash_count"] == 1
    assert current["complete"] is False


def test_household_history_uses_only_complete_exact_stichtags_and_supports_year_change():
    conn = base_db()
    for day, value in (("2026-01-01", "100.00"), ("2026-08-01", "120.00")):
        confirm_cash_snapshot(
            conn,
            CashSnapshotConfirmRequest(
                account_id="bank-a",
                snapshot_type="manual_balance",
                balance_date=day,
                amount_chf=value,
                note="synthetic",
                preview_id=f"preview-{day}",
                confirm=True,
            ),
        )
    current = {
        "complete": True,
        "total": Decimal("120"),
        "distribution": [
            {"key": "cash", "value_chf": "120.00"},
            {"key": "equity", "value_chf": "0.00"},
            {"key": "truewealth", "value_chf": None},
            {"key": "crypto", "value_chf": "0.00"},
        ],
    }

    points, reason = cockpit._household_history(
        conn,
        from_date=cockpit.date(2026, 1, 1),
        to_date=cockpit.date(2026, 8, 1),
        current=current,
        as_of=cockpit.date(2026, 8, 1),
    )

    assert points == [
        {"at": "2026-01-01", "value_chf": "100.00"},
        {"at": "2026-08-01", "value_chf": "120.00"},
    ]
    assert "nicht ergänzt" in reason


def test_household_history_uses_canonical_snapshot_value_and_fx_columns():
    conn = base_db()
    conn.execute("UPDATE accounts SET is_active=0 WHERE account_id='bank-a'")
    set_performance_scope_classification(
        conn,
        account_id="depot",
        included=True,
        classification_role="crypto_portfolio",
        source="test",
        note="synthetic classified account",
        classified_at=NOW,
    )
    add_valuation(conn, "fx-open", "2026-01-01", "100", currency="EUR", fx="2")
    add_valuation(conn, "fx-close", "2026-08-01", "120", currency="EUR", fx="2")

    points, reason = cockpit._household_history(
        conn,
        from_date=cockpit.date(2026, 1, 1),
        to_date=cockpit.date(2026, 8, 1),
        current={
            "complete": False,
            "total": Decimal("240"),
            "distribution": [
                {"key": "cash", "value_chf": None},
                {"key": "equity", "value_chf": None},
                {"key": "truewealth", "value_chf": None},
                {"key": "crypto", "value_chf": "240.00"},
            ],
        },
        as_of=cockpit.date(2026, 8, 1),
    )

    assert points == [
        {"at": "2026-01-01", "value_chf": "200.00"},
        {"at": "2026-08-01", "value_chf": "240.00"},
    ]
    assert "nicht ergänzt" in reason


def test_normal_cockpit_get_is_read_only_and_uses_only_stored_data(monkeypatch):
    import socket

    def reject_provider_call(*args, **kwargs):
        raise AssertionError("normal wealth rendering must not open a network connection")

    monkeypatch.setattr(socket.socket, "connect", reject_provider_call)
    conn = base_db()
    before = conn.total_changes
    payload = wealth_cockpit_endpoint(
        period="1m", as_of="2026-08-01", conn=conn
    )

    assert len(payload["kpis"]) == 6
    assert payload["modelled_development"]["method"] == "modelled_wealth_daily_v1"
    assert payload["verified_performance"]["status"] in {"verified", "not_verified"}
    assert payload["not_net_worth"] is True
    assert payload["planning"]["included_in_wealth"] is False
    validated = WealthCockpitResponse.model_validate(payload)
    assert validated.readiness.dimensions.current_value.status in {
        "ready",
        "partial",
        "not_ready",
    }
    assert conn.total_changes == before


def test_ytd_is_forwarded_to_the_modelled_wealth_series(monkeypatch):
    conn = base_db()
    observed: list[str] = []
    original = cockpit.build_modelled_wealth_development

    def capture_modelled(conn, *, period, as_of=None):
        observed.append(period)
        return original(conn, period=period, as_of=as_of)

    monkeypatch.setattr(cockpit, "build_modelled_wealth_development", capture_modelled)
    cockpit.build_wealth_cockpit(conn, period="ytd", as_of="2026-08-01")

    assert observed == ["ytd"]


def test_previous_year_coverage_uses_the_selected_period_end(monkeypatch):
    conn = base_db()
    observed: dict[str, str | None] = {}
    original = cockpit.build_performance_coverage

    def capture_coverage(conn, *, from_date=None, to_date=None):
        observed.update(from_date=from_date, to_date=to_date)
        return original(conn, from_date=from_date, to_date=to_date)

    monkeypatch.setattr(cockpit, "build_performance_coverage", capture_coverage)

    cockpit.build_wealth_cockpit(
        conn,
        period="previous_year",
        as_of="2026-08-02",
    )

    assert observed == {"from_date": "2025-01-01", "to_date": "2025-12-31"}


def test_current_value_performance_and_policy_readiness_are_independent():
    current = {
        "complete": False,
        "data_as_of": "2026-07-31",
        "sources": [
            {
                "label": "Known source",
                "current_value_chf": "100.00",
                "current_value_status": "ready",
                "freshness_status": "fresh",
            },
            {
                "label": "Missing source",
                "current_value_chf": None,
                "current_value_status": "not_ready",
                "freshness_status": "unavailable",
            },
        ],
    }
    coverage = {
        "rows": [
            {
                "scope": scope,
                "ttwror_status": "complete",
                "xirr_status": "complete",
                "attribution_status": "complete",
                "scope_classification_status": "complete",
                "cashflow_coverage_status": "complete",
            }
            for scope in ("postfinance", "truewealth", "crypto")
        ]
    }
    readiness = cockpit._build_readiness(
        current=current,
        coverage=coverage,
        policy={"configured": False},
        period={"preset": "ytd", "from": "2026-01-01", "to": "2026-08-02"},
        summary={
            "net_external_cashflows": "10.00",
            "investment_result": "5.00",
        },
        ttwror_quality={"status": "complete"},
        household_change=None,
        history_points=[],
        reconciliation_status="difference",
    )

    dimensions = readiness["dimensions"]
    assert dimensions["current_value"]["status"] == "partial"
    assert dimensions["freshness"]["status"] == "partial"
    assert dimensions["reconciliation"]["status"] == "not_ready"
    assert dimensions["performance"]["status"] == "ready"
    assert readiness["dimensions"]["policy"]["status"] == "not_applicable"
    assert next(row for row in readiness["metrics"] if row["key"] == "ttwror")[
        "status"
    ] == "ready"
    net = next(row for row in readiness["metrics"] if row["key"] == "net_contributions")
    assert net["status"] == "ready"
    assert net["included_sources"] == ["PostFinance", "True Wealth", "Kryptowährungen"]
    assert net["missing_sources"] == []


def test_source_performance_status_is_partial_when_only_some_metrics_are_ready():
    assert cockpit._performance_scope_status(
        {
            "ttwror_status": "complete",
            "xirr_status": "unavailable",
            "attribution_status": "unavailable",
        }
    ) == "partial"


def test_diagnostics_name_sources_deduplicate_and_limit_prominent_hints():
    current = {
        "sources": [
            {
                "label": "AKB Haushaltskonto",
                "provider_label": "AKB",
                "kind": "Bankguthaben",
                "current_value_chf": None,
                "freshness_status": "unavailable",
            },
            {
                "label": "AKB Rückstellungen",
                "provider_label": "AKB",
                "kind": "Bankguthaben",
                "current_value_chf": None,
                "freshness_status": "unavailable",
            },
            {
                "label": "Kryptowährungen",
                "provider_label": "Kryptowährungen",
                "kind": "Anlage",
                "current_value_chf": "5.00",
                "freshness_status": "stale",
            },
        ]
    }
    coverage = {
        "rows": [
            {
                "scope": "postfinance",
                "valuation_from": "2026-05-01",
                "reason_codes": ["external_cashflow_history_missing"],
            },
            {
                "scope": "truewealth",
                "valuation_from": "2026-06-01",
                "reason_codes": ["external_cashflow_history_missing"],
            },
            {
                "scope": "crypto",
                "valuation_from": None,
                "reason_codes": ["external_cashflow_history_missing"],
            },
        ]
    }
    diagnostics = cockpit._build_diagnostics(
        current=current,
        coverage=coverage,
        policy={"configured": False},
        period={"preset": "ytd", "from": "2026-01-01", "to": "2026-08-02"},
    )

    identities = {
        (row["dimension"], tuple(row["affected_sources"]), row["reason_code"])
        for row in diagnostics
    }
    assert len(identities) == len(diagnostics)
    assert sum(row["prominent"] for row in diagnostics) == 3
    bank = next(row for row in diagnostics if row["reason_code"] == "current_bank_balance_missing")
    assert bank["affected_sources"] == ["AKB"]
    assert all(row["affected_sources"] or row["dimension"] == "policy" for row in diagnostics)


def test_cash_sources_group_currencies_by_real_account_not_provider(monkeypatch):
    positions = [
        SimpleNamespace(
            platform="AKB",
            account_label="AKB Haushaltskonto",
            amount_chf="10.00",
            last_manual_reconciliation="2026-08-01",
            last_imported_booking=None,
            status="Aktuell",
            balance_mode="snapshots",
        ),
        SimpleNamespace(
            platform="AKB",
            account_label="AKB Haushaltskonto",
            amount_chf="2.00",
            last_manual_reconciliation="2026-08-01",
            last_imported_booking=None,
            status="Aktuell",
            balance_mode="snapshots",
        ),
        SimpleNamespace(
            platform="AKB",
            account_label="AKB Rückstellungen",
            amount_chf="3.00",
            last_manual_reconciliation="2026-08-01",
            last_imported_booking=None,
            status="Aktuell",
            balance_mode="snapshots",
        ),
    ]
    monkeypatch.setattr(
        cockpit,
        "get_cash_summary",
        lambda conn: SimpleNamespace(cash_chf="15.00", positions=positions),
    )
    monkeypatch.setattr(
        cockpit,
        "get_equity_summary",
        lambda conn: SimpleNamespace(
            valued_partial_chf="0.00",
            coverage_complete=True,
            unvalued_positions=0,
            as_of=None,
        ),
    )
    monkeypatch.setattr(cockpit, "list_crypto_positions", lambda conn: [])
    monkeypatch.setattr(cockpit, "_truewealth", lambda conn: (None, None))
    monkeypatch.setattr(cockpit, "_unassigned_values", lambda conn: (Decimal("0"), []))

    current = cockpit._current_values(base_db(), as_of=cockpit.date(2026, 8, 2))
    bank_sources = [row for row in current["sources"] if row["kind"] == "Bankguthaben"]

    assert [row["label"] for row in bank_sources] == [
        "AKB Haushaltskonto",
        "AKB Rückstellungen",
    ]
    assert bank_sources[0]["current_value_chf"] == "12.00"
    assert bank_sources[0]["current_value_status"] == "ready"
    assert all(row["source_role"] == "account" for row in bank_sources)


def test_known_stale_crypto_value_remains_visible_with_business_date(monkeypatch):
    monkeypatch.setattr(
        cockpit,
        "get_cash_summary",
        lambda conn: SimpleNamespace(cash_chf="0.00", positions=[]),
    )
    monkeypatch.setattr(
        cockpit,
        "get_equity_summary",
        lambda conn: SimpleNamespace(
            valued_partial_chf="0.00",
            coverage_complete=True,
            unvalued_positions=0,
            as_of=None,
        ),
    )
    monkeypatch.setattr(
        cockpit,
        "list_crypto_positions",
        lambda conn: [
            SimpleNamespace(
                market_value_chf="50.00",
                last_price_update="2026-05-15T12:00:00+00:00",
            )
        ],
    )
    monkeypatch.setattr(cockpit, "_truewealth", lambda conn: (None, None))
    monkeypatch.setattr(cockpit, "_unassigned_values", lambda conn: (Decimal("0"), []))

    current = cockpit._current_values(base_db(), as_of=cockpit.date(2026, 8, 2))
    crypto = next(row for row in current["sources"] if row["key"] == "crypto")

    assert crypto["current_value_chf"] == "50.00"
    assert crypto["current_value_status"] == "ready"
    assert crypto["as_of"] == "2026-05-15T12:00:00+00:00"
    assert crypto["freshness_status"] == "stale"
