from __future__ import annotations

import json
import sqlite3
from decimal import Decimal
from typing import Any, cast

import pytest
from fastapi.testclient import TestClient

import jarvis_finance.services.truewealth_productization as truewealth_productization

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.api.schemas.performance_activation import TrueWealthBankPaymentConfirmResponse
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,
    load_scope_activities,
)
from jarvis_finance.services.truewealth_productization import (
    activate_truewealth_recipient_rule,
    confirm_truewealth_bank_payments,
    preview_truewealth_bank_payments,
    process_activated_truewealth_bank_payments,
)
from jarvis_finance.services.truewealth_valuation import (
    build_truewealth_activation_performance_preview,
)
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

NOW = "2026-08-03T12:00:00+00:00"
HASH = "a" * 64
ATTESTATION = "Diese Empfängeridentität gehört zum persönlichen True-Wealth-Portfolio."


def database():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute("INSERT INTO platforms(platform_id,name,platform_type,created_at) VALUES('tw','True Wealth','broker',?)", (NOW,))
    conn.execute(
        """INSERT INTO accounts(account_id,platform_id,account_name,account_type,currency,
                   performance_included,is_active,created_at)
           VALUES('tw-total','tw','True Wealth','brokerage','CHF',0,1,?)""",
        (NOW,),
    )
    set_performance_scope_classification(
        conn, account_id="tw-total", included=True,
        classification_role="canonical_truewealth_total_value", source="test",
        note="test", classified_at=NOW,
    )
    conn.execute(
        """INSERT INTO budget_accounts(budget_account_id,name,account_type,currency,is_active,created_at)
           VALUES('rai','Raiffeisen Mitglieder-Privatkonto','checking','CHF',1,?)""",
        (NOW,),
    )
    conn.commit()
    return conn


def add_bank_row(conn, row_id: str, day: str, amount: str, *, payee: str = "Dauerauftrag Example Recipient", counterparty_hash: str = HASH, raw_fingerprint: str | None = None, transaction_type: str = "transfer"):
    candidate_id = f"candidate-{row_id}"
    fingerprint = raw_fingerprint or f"raw-{row_id}"
    conn.execute(
        """INSERT INTO budget_transaction_candidates(
             transaction_candidate_id,source_file_label,source_type,transaction_date,description,merchant,
             amount_original,currency_original,status,notes,created_at,account_source,raw_fingerprint)
           VALUES(?,?,'raiffeisen_bank',?,?,?,?,'CHF','confirmed',?,?,?,?)""",
        (
            candidate_id, "raiffeisen.csv", day,
            f"Dauerauftrag {payee} CHF {amount}", payee, f"-{amount}",
            json.dumps({"counterparty_hash": counterparty_hash}), NOW, "rai", fingerprint,
        ),
    )
    conn.execute(
        """INSERT INTO budget_transactions(
             budget_transaction_id,account_id,transaction_type,transaction_date,booking_date,description,payee,
             amount_original,currency_original,fx_rate_to_chf,amount_chf,fx_status,status,source_type,
             created_at,source_candidate_id)
           VALUES(?,'rai',?,?,?,?,?,?,'CHF','1',?,'not_needed','confirmed','import_candidate',?,?)""",
        (
            row_id, transaction_type, day, day, f"Dauerauftrag {payee} CHF {amount}", payee,
            f"-{amount}", f"-{amount}", NOW, candidate_id,
        ),
    )
    conn.commit()


def preview(conn):
    return preview_truewealth_bank_payments(
        conn,
        period_from="2026-01-01",
        period_to="2026-07-31",
        recipient_name="Example Recipient",
        recipient_account_identity=None,
        counterparty_hashes=[HASH],
        reference_tokens=[],
        attestation=ATTESTATION,
    )


def confirmation_request(result, confirmation_id="confirm-bank-rule"):
    return {
        "period_from": result["period_from"],
        "period_to": result["period_to"],
        "recipient_name": "Example Recipient",
        "recipient_account_identity": None,
        "counterparty_hashes": [HASH],
        "reference_tokens": [],
        "attestation": ATTESTATION,
        "preview_id": result["preview_id"],
        "input_fingerprint": result["input_fingerprint"],
        "confirmation_id": confirmation_id,
        "confirm": True,
    }


def test_recipient_identity_detects_changed_amount_without_using_amount_or_rhythm():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    add_bank_row(conn, "bank-2", "2026-02-27", "600")
    add_bank_row(conn, "bank-3", "2026-06-30", "900")
    add_bank_row(conn, "other", "2026-07-01", "900", payee="Different Beneficiary", counterparty_hash="b" * 64)

    before = conn.total_changes
    result = preview(conn)

    assert conn.total_changes == before
    assert [row["amount"] for row in result["secure_candidates"]] == ["600", "600", "900"]
    assert result["detected_amount_change"] == {
        "detected": True,
        "amounts": ["600", "900"],
        "rule_uses_amount": False,
    }
    assert result["counts"] == {"secure": 3, "uncertain": 0, "excluded": 1, "new": 3, "existing": 0, "duplicates": 0}
    assert result["excluded_candidates"][0]["amount"] == "900"
    assert result["excluded_candidates"][0]["overall_wealth_treatment"] == "not_applicable_excluded"
    assert result["excluded_candidates"][0]["household_treatment"] == "unchanged_excluded"
    assert all(row["overall_wealth_treatment"] == "internal_wealth_transfer" for row in result["secure_candidates"])
    assert all(row["truewealth_scope_treatment"] == "external_deposit" for row in result["secure_candidates"])
    assert all(row["household_treatment"] == "not_expense_not_income" for row in result["secure_candidates"])
    assert all(row["date_basis"] == "bank_value_date_proxy" for row in result["secure_candidates"])


def test_duplicate_bank_row_and_existing_manual_cashflow_are_not_written_twice():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", raw_fingerprint="same")
    add_bank_row(conn, "bank-duplicate", "2026-01-30", "600", raw_fingerprint="same")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,external_transaction_id,is_confirmed,quality_status,created_at)
           VALUES('manual','external_deposit','external_deposit','tw-total','2026-01-30','600','600','CHF','1',
                  'identity','ok','600','600','truewealth_external_cashflow_v1','manual','same',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()

    result = preview(conn)

    assert [row["disposition"] for row in result["secure_candidates"]] == ["existing", "duplicate"]
    assert result["cashflows_to_write"] == []
    assert result["counts"]["duplicates"] == 1
    assert result["counts"]["existing"] == 1


def test_same_date_amount_without_exact_lineage_requires_review():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", raw_fingerprint="bank-lineage")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,is_confirmed,quality_status,created_at)
           VALUES('manual-other','external_deposit','external_deposit','tw-total','2026-01-30','600','600','CHF','1',
                  'identity','ok','600','600','truewealth_external_cashflow_v1','manual-other',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()

    result = preview(conn)

    assert result["counts"]["secure"] == 0
    assert result["counts"]["uncertain"] == 1
    assert result["can_confirm"] is False
    assert "economic_collision_without_exact_lineage" in result["uncertain_candidates"][0]["recognition_reasons"]


def test_collision_decisions_are_explicit_fingerprint_bound_and_fail_closed():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", raw_fingerprint="bank-lineage")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,is_confirmed,quality_status,created_at)
           VALUES('manual-other','external_deposit','external_deposit','tw-total','2026-01-30','600','600','CHF','1',
                  'identity','ok','600','600','truewealth_external_cashflow_v1','manual-other',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()

    linked = preview_truewealth_bank_payments(
        conn,
        period_from="2026-01-01", period_to="2026-07-31",
        recipient_name="Example Recipient", recipient_account_identity=None,
        counterparty_hashes=[HASH], reference_tokens=[], attestation=ATTESTATION,
        collision_decisions={"bank-1": "link_existing"},
    )
    assert linked["counts"]["existing"] == 1
    assert linked["cashflows_to_write"] == []

    distinct = preview_truewealth_bank_payments(
        conn,
        period_from="2026-01-01", period_to="2026-07-31",
        recipient_name="Example Recipient", recipient_account_identity=None,
        counterparty_hashes=[HASH], reference_tokens=[], attestation=ATTESTATION,
        collision_decisions={"bank-1": "record_distinct"},
    )
    assert distinct["can_confirm"] is True
    assert distinct["counts"]["new"] == 1
    request = confirmation_request(distinct, "confirm-distinct")
    request["collision_decisions"] = {"bank-1": "record_distinct"}
    assert confirm_truewealth_bank_payments(conn, request)["written_cashflows"] == 1

    with pytest.raises(ValueError, match="current unresolved economic collision"):
        preview_truewealth_bank_payments(
            conn,
            period_from="2026-01-01", period_to="2026-07-31",
            recipient_name="Example Recipient", recipient_account_identity=None,
            counterparty_hashes=[HASH], reference_tokens=[], attestation=ATTESTATION,
            collision_decisions={"unknown": "record_distinct"},
        )


@pytest.mark.parametrize(
    ("mutation", "reason", "expected_bucket"),
    [
        (
            "UPDATE budget_accounts SET account_type='credit_card' WHERE budget_account_id='rai'",
            "raiffeisen_cash_account_role_required",
            "uncertain_candidates",
        ),
        ("UPDATE budget_transaction_candidates SET source_type='csv_seed'", None, None),
        (
            "UPDATE budget_transactions SET currency_original='USD',amount_chf=NULL,fx_status='missing'",
            "confirmed_chf_amount_required",
            "uncertain_candidates",
        ),
    ],
)
def test_bank_source_role_and_currency_are_validated_fail_closed(
    mutation, reason, expected_bucket
):
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    conn.execute(mutation)
    conn.commit()

    result = preview(conn)

    assert result["can_confirm"] is False
    assert result["counts"]["secure"] == 0
    if expected_bucket:
        assert result["counts"]["uncertain"] == 1
        assert reason in result[expected_bucket][0]["recognition_reasons"]
    else:
        assert result["counts"]["uncertain"] == 0
        assert result["excluded_candidates"] == []


def test_bank_detection_uses_stable_source_not_account_display_name():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    conn.execute(
        "UPDATE budget_accounts SET name='Renamed household cash account' WHERE budget_account_id='rai'"
    )
    conn.commit()

    result = preview(conn)

    assert result["counts"]["secure"] == 1
    assert result["can_confirm"] is True


def test_matching_row_with_unresolved_fx_blocks_otherwise_confirmable_batch():
    conn = database()
    add_bank_row(conn, "bank-ok", "2026-01-30", "600")
    add_bank_row(conn, "bank-fx", "2026-02-28", "900")
    conn.execute(
        """UPDATE budget_transactions
           SET currency_original='USD', amount_chf=NULL, fx_status='missing'
           WHERE budget_transaction_id='bank-fx'"""
    )
    conn.commit()

    result = preview(conn)

    assert result["counts"]["secure"] == 1
    assert result["counts"]["uncertain"] == 1
    assert result["can_confirm"] is False
    assert "confirmed_chf_amount_required" in result["uncertain_candidates"][0][
        "recognition_reasons"
    ]


def test_confirm_rolls_back_on_unrelated_transaction_id_collision(monkeypatch):
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,is_confirmed,quality_status,created_at)
           VALUES('occupied-id','fee','fee','tw-total','2026-01-01','0','0','CHF','1',
                  'identity','ok','0','0','manual','unrelated',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()
    result = preview(conn)
    request = confirmation_request(result, confirmation_id="collision-confirm")
    original_stable_id = truewealth_productization.stable_id
    monkeypatch.setattr(
        truewealth_productization,
        "stable_id",
        lambda prefix, *parts: (
            "occupied-id" if prefix == "tw-bank-flow" else original_stable_id(prefix, *parts)
        ),
    )

    with pytest.raises(sqlite3.IntegrityError):
        confirm_truewealth_bank_payments(conn, request)

    assert conn.execute(
        "SELECT COUNT(*) FROM transactions WHERE source_type=?",
        (truewealth_productization.SOURCE,),
    ).fetchone()[0] == 0
    assert conn.execute(
        "SELECT COUNT(*) FROM audit_log WHERE action='recipient_rule_and_history_confirmed'"
    ).fetchone()[0] == 0


@pytest.mark.parametrize(
    ("amount_chf", "fx_rate", "reason"),
    [
        ("-1", "1", "chf_amount_inconsistent_with_original"),
        ("-600", "2", "chf_identity_fx_rate_required"),
    ],
)
def test_chf_cashflow_requires_consistent_amount_and_identity_fx_rate(amount_chf, fx_rate, reason):
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    conn.execute(
        "UPDATE budget_transactions SET amount_chf=?,fx_rate_to_chf=? WHERE budget_transaction_id='bank-1'",
        (amount_chf, fx_rate),
    )
    conn.commit()

    result = preview(conn)

    assert result["can_confirm"] is False
    assert result["counts"]["secure"] == 0
    assert reason in result["uncertain_candidates"][0]["recognition_reasons"]


def test_confirmed_link_existing_decision_is_durable_lineage():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", raw_fingerprint="bank-lineage")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,is_confirmed,quality_status,created_at)
           VALUES('existing-economic','external_deposit','external_deposit','tw-total','2026-01-30','600','600','CHF','1',
                  'identity','ok','600','600','truewealth_external_cashflow_v1','manual-lineage',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()
    decided = preview_truewealth_bank_payments(
        conn,
        period_from="2026-01-01",
        period_to="2026-07-31",
        recipient_name="Example Recipient",
        recipient_account_identity=None,
        counterparty_hashes=[HASH],
        reference_tokens=[],
        attestation=ATTESTATION,
        collision_decisions={"bank-1": "link_existing"},
    )
    request = {
        **confirmation_request(decided, confirmation_id="confirm-link"),
        "collision_decisions": {"bank-1": "link_existing"},
    }

    confirmed = confirm_truewealth_bank_payments(conn, request)
    refreshed = preview(conn)
    audit = json.loads(
        conn.execute(
            "SELECT new_values_json FROM audit_log WHERE action='recipient_rule_and_history_confirmed'"
        ).fetchone()[0]
    )

    assert confirmed["written_cashflows"] == 0
    assert refreshed["can_confirm"] is True
    assert refreshed["secure_candidates"][0]["disposition"] == "existing"
    assert "economic_collision_linked_by_confirmed_lineage" in refreshed["secure_candidates"][0][
        "recognition_reasons"
    ]
    assert audit["collision_links"] == [
        {"bank_transaction_id": "bank-1", "existing_cashflow_id": "existing-economic"}
    ]

    conn.execute("UPDATE transactions SET is_voided=1 WHERE transaction_id='existing-economic'")
    conn.commit()
    invalidated = preview(conn)
    assert invalidated["can_confirm"] is False
    assert invalidated["counts"]["secure"] == 0
    assert invalidated["uncertain_candidates"][0]["disposition"] == "unclear"
    assert "confirmed_collision_link_invalid" in invalidated["uncertain_candidates"][0]["recognition_reasons"]


def test_collision_decision_cannot_promote_weak_recipient_identity():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", raw_fingerprint="bank-lineage")
    conn.execute(
        """INSERT INTO transactions(transaction_id,transaction_type,activity_kind,account_id,trade_date,
                   gross_amount_original,net_amount_original,currency_original,fx_rate_to_chf,fx_source,fx_status,
                   gross_amount_chf,net_amount_chf,source_type,source_id,is_confirmed,quality_status,created_at)
           VALUES('existing-economic','external_deposit','external_deposit','tw-total','2026-01-30','600','600','CHF','1',
                  'identity','ok','600','600','truewealth_external_cashflow_v1','manual-lineage',1,'complete',?)""",
        (NOW,),
    )
    conn.commit()

    with pytest.raises(ValueError, match="cannot substitute for stable recipient identity"):
        preview_truewealth_bank_payments(
            conn,
            period_from="2026-01-01",
            period_to="2026-12-31",
            recipient_name="Dauerauftrag Example Recipient",
            recipient_account_identity=None,
            counterparty_hashes=["b" * 64],
            reference_tokens=[],
            attestation=ATTESTATION,
            collision_decisions={"bank-1": "record_distinct"},
        )


def test_confirm_is_fingerprint_bound_idempotent_audited_and_does_not_activate_rule():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    result = preview(conn)
    request = confirmation_request(result)

    confirmed = confirm_truewealth_bank_payments(conn, request)
    repeated = confirm_truewealth_bank_payments(conn, request)
    altered_replay = {**request, "recipient_name": "Different Recipient"}

    assert confirmed["written_cashflows"] == 1
    assert confirmed["rule_activated"] is False
    assert repeated["idempotent"] is True
    with pytest.raises(ValueError, match="already used with a different payload"):
        confirm_truewealth_bank_payments(conn, altered_replay)
    TrueWealthBankPaymentConfirmResponse.model_validate(confirmed)
    TrueWealthBankPaymentConfirmResponse.model_validate(repeated)
    assert conn.execute("SELECT COUNT(*) FROM transactions WHERE source_type='truewealth_raiffeisen_cashflow_v1'").fetchone()[0] == 1
    assert conn.execute("SELECT COUNT(*) FROM audit_log WHERE action='recipient_rule_activated'").fetchone()[0] == 0
    assert conn.execute("SELECT COUNT(*) FROM performance_cashflow_coverage").fetchone()[0] == 0
    audit_payload = conn.execute(
        "SELECT new_values_json FROM audit_log WHERE action='recipient_rule_and_history_confirmed'"
    ).fetchone()[0]
    assert "Dauerauftrag" not in audit_payload
    assert ATTESTATION not in audit_payload
    assert '"description"' not in audit_payload
    assert conn.execute("SELECT transaction_type FROM budget_transactions WHERE budget_transaction_id='bank-1'").fetchone()[0] == "transfer"
    activity = load_scope_activities(conn, account_ids=["tw-total"], to_date="2026-07-31", data_cutoff="2026-08-04T00:00:00+00:00")
    assert [(row.kind, str(row.net)) for row in activity] == [("external_deposit", "600")]

    # Rule activation is a separate explicit operation from historical cashflow confirmation.
    activated = activate_truewealth_recipient_rule(
        conn,
        {"activation_id": "activate-rule", "rule_fingerprint": confirmed["rule_fingerprint"], "confirm": True},
    )
    assert activated["active"] is True
    assert activate_truewealth_recipient_rule(
        conn,
        {"activation_id": "activate-rule", "rule_fingerprint": confirmed["rule_fingerprint"], "confirm": True},
    )["idempotent"] is True


def test_confirmed_payment_enters_canonical_ttwror_and_xirr_only_after_separate_coverage():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    bank_preview = preview(conn)
    confirm_truewealth_bank_payments(conn, confirmation_request(bank_preview))
    for snapshot_id, day, value in (
        ("open", "2026-01-01", "1000"),
        ("flow", "2026-01-30", "1600"),
        ("close", "2026-07-31", "1800"),
    ):
        conn.execute(
            """INSERT INTO account_value_snapshots(snapshot_id,account_id,valuation_date,total_value_chf,currency,source_type,quality_status,created_at,source_reference,is_active)
               VALUES(?,?,?,?,'CHF','truewealth_official_import','ok',?,?,1)""",
            (snapshot_id, "tw-total", day, value, NOW, snapshot_id),
        )
    refreshed_bank_preview = preview(conn)
    activation_preview = build_truewealth_activation_performance_preview(
        conn,
        bank_preview=refreshed_bank_preview,
        period_from="2026-01-01",
        period_to="2026-07-31",
    )
    assert refreshed_bank_preview["counts"]["existing"] == 1
    assert refreshed_bank_preview["cashflows_to_write"] == []
    assert Decimal(activation_preview["deposit_total_chf"]) == Decimal(600)
    assert len(activation_preview["deposits"]) == 1
    set_performance_cashflow_coverage(
        conn,
        account_id="tw-total",
        coverage_from="2026-01-01",
        coverage_to="2026-07-31",
        status="complete",
        source="explicit_test_attestation",
        note="All external cashflows independently attested for this test period.",
        recorded_at=NOW,
    )
    conn.commit()

    performance = build_portfolio_performance(
        conn,
        from_date="2026-01-01",
        to_date="2026-07-31",
        account_id="tw-total",
        data_cutoff="2026-08-04T00:00:00+00:00",
    )

    summary = cast(dict[str, Any], performance["summary"])
    quality = cast(dict[str, dict[str, Any]], performance["quality"])
    assert Decimal(str(summary["net_external_cashflows"])) == Decimal(600)
    assert Decimal(str(summary["investment_result"])) == Decimal(200)
    assert summary["ttwror_cumulative"] is not None, performance
    assert summary["xirr_annualized"] is not None
    assert quality["ttwror"]["status"] == "complete"
    assert quality["xirr"]["status"] == "complete"


def test_activated_rule_applies_only_unambiguous_future_rows_idempotently():
    conn = database()
    add_bank_row(conn, "bank-old", "2026-01-30", "600")
    historical = preview(conn)
    confirmed = confirm_truewealth_bank_payments(conn, confirmation_request(historical))
    activate_truewealth_recipient_rule(
        conn,
        {"activation_id": "activate-rule", "rule_fingerprint": confirmed["rule_fingerprint"], "confirm": True},
    )
    add_bank_row(conn, "bank-future", "2026-08-03", "900")

    applied = process_activated_truewealth_bank_payments(conn, as_of="2026-08-03")
    replay = process_activated_truewealth_bank_payments(conn, as_of="2026-08-03")

    assert applied["status"] == "complete"
    assert applied["written_cashflows"] == 1
    assert applied["coverage_activated"] is False
    assert replay["written_cashflows"] == 0
    assert conn.execute("SELECT COUNT(*) FROM transactions WHERE source_type='truewealth_raiffeisen_cashflow_v1'").fetchone()[0] == 2

    add_bank_row(
        conn,
        "bank-ambiguous",
        "2026-08-04",
        "900",
        payee="Example Other",
        counterparty_hash="c" * 64,
    )
    blocked = process_activated_truewealth_bank_payments(conn, as_of="2026-08-04")
    assert blocked["status"] == "no_candidates"
    assert blocked["written_cashflows"] == 0


def test_uncertain_partial_name_match_is_never_confirmable():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", payee="Example Other", counterparty_hash="c" * 64)
    result = preview(conn)
    assert result["counts"]["secure"] == 0
    assert result["counts"]["uncertain"] == 1
    assert result["can_confirm"] is False
    with pytest.raises(ValueError, match="unresolved"):
        confirm_truewealth_bank_payments(conn, confirmation_request(result))


def test_recipient_name_substring_with_different_identity_is_never_secure():
    conn = database()
    add_bank_row(
        conn,
        "bank-1",
        "2026-01-30",
        "600",
        payee="Example Recipient Scam",
        counterparty_hash="c" * 64,
    )

    result = preview(conn)

    assert result["counts"]["secure"] == 0
    assert result["counts"]["uncertain"] == 1
    assert result["can_confirm"] is False


def test_short_or_superset_account_and_reference_tokens_cannot_secure_match():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600", payee="Recipient Scammer", counterparty_hash="c" * 64)
    with pytest.raises(ValueError, match="too short"):
        preview_truewealth_bank_payments(
            conn,
            period_from="2026-01-01",
            period_to="2026-07-31",
            recipient_name="",
            recipient_account_identity="123",
            counterparty_hashes=[],
            reference_tokens=[],
            attestation=ATTESTATION,
        )
    result = preview_truewealth_bank_payments(
        conn,
        period_from="2026-01-01",
        period_to="2026-07-31",
        recipient_name="",
        recipient_account_identity=None,
        counterparty_hashes=[],
        reference_tokens=["recipient scam"],
        attestation=ATTESTATION,
    )
    assert result["counts"]["secure"] == 0


def test_read_only_api_allows_preview_but_blocks_confirm_without_mutation():
    conn = database()
    add_bank_row(conn, "bank-1", "2026-01-30", "600")
    app = create_app(write_mode="disabled")
    app.dependency_overrides[get_db] = lambda: conn
    client = TestClient(app)
    body = {
        "period_from": "2026-01-01",
        "period_to": "2026-07-31",
        "recipient_name": "Example Recipient",
        "recipient_account_identity": None,
        "counterparty_hashes": ["a" * 64],
        "reference_tokens": [],
        "attestation": "Preview only; no recipient rule or cashflow is activated.",
    }
    before = conn.total_changes
    response = client.post(
        "/api/portfolio/performance/truewealth-bank-payments/preview",
        json=body,
    )
    assert response.status_code == 200
    assert response.json()["counts"]["secure"] == 1
    assert conn.total_changes == before
    for path in (
        "/api/portfolio/performance/truewealth/view?requested_from=2026-01-01&requested_to=2026-07-31",
        "/api/portfolio/performance/truewealth/model-preview?as_of=2026-07-31",
    ):
        readonly = client.get(path)
        assert readonly.status_code == 200, readonly.text
        assert conn.total_changes == before
    package = client.post(
        "/api/portfolio/performance/truewealth/activation-package-preview",
        json={
            **body,
            "performance_from": "2026-01-01",
            "performance_to": "2026-07-31",
            "model_as_of": "2026-07-31",
        },
    )
    assert package.status_code == 200, package.text
    assert package.json()["activation_package"]["production_writes"] == 0
    model_payload = client.get(
        "/api/portfolio/performance/truewealth/model-preview?as_of=2026-07-31"
    ).json()
    assert set(model_payload) == {
        "as_of", "account_id", "status", "quality_status", "reason_codes",
        "modelled_value_chf", "latest_price_fx_date", "latest_modelled_value",
        "input_fingerprint",
    }
    assert not ({"positions", "provenance", "anchor", "cashflows"} & set(model_payload))
    assert set(package.json()["modelled_valuation"]) == set(model_payload)
    setup_response = client.get("/api/portfolio/performance/setup")
    assert setup_response.status_code == 200
    setup_payload = setup_response.json()
    assert set(setup_payload) == {"status", "source_order", "sources", "timer_enabled"}
    assert all(
        set(item["diagnostics"])
        == {
            "reason_codes",
            "valuation_dates",
            "position_dates",
            "reclassification_status",
            "component_status",
        }
        for item in setup_payload["sources"]
    )
    setup_text = json.dumps(setup_payload)
    for forbidden in ("instrument_id", "source_reference", "row_hash", "provenance"):
        assert forbidden not in setup_text
    assert conn.total_changes == before
    openapi = client.get("/openapi.json").json()
    assert "$ref" in openapi["paths"]["/api/portfolio/performance/truewealth/view"]["get"]["responses"]["200"]["content"]["application/json"]["schema"]
    assert "$ref" in openapi["paths"]["/api/portfolio/performance/truewealth/model-preview"]["get"]["responses"]["200"]["content"]["application/json"]["schema"]
    for schema_name in (
        "TrueWealthModelPreviewResponse",
        "TrueWealthActivationPerformance",
        "TrueWealthActivationPackage",
        "TrueWealthBankPaymentCandidate",
        "PerformanceSetupDiagnostics",
    ):
        assert openapi["components"]["schemas"][schema_name]["additionalProperties"] is False
    confirm = client.post(
        "/api/portfolio/performance/truewealth-bank-payments/confirm",
        json={
            **body,
            "preview_id": response.json()["preview_id"],
            "input_fingerprint": response.json()["input_fingerprint"],
            "confirmation_id": "blocked-in-read-only",
            "confirm": True,
        },
    )
    assert confirm.status_code == 403
    activation_path = "/api/portfolio/performance/truewealth-recipient-rule/activate"
    assert activation_path in openapi["paths"]
    activation = client.post(
        activation_path,
        json={"activation_id": "blocked", "rule_fingerprint": "a" * 64, "confirm": True},
    )
    assert activation.status_code == 403
    assert conn.total_changes == before
