from __future__ import annotations

from datetime import UTC, datetime
from decimal import Decimal

from fastapi.testclient import TestClient

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.quality.freshness import combined_freshness
from jarvis_finance.services.reconciliation_snapshot import build_reconciliation_snapshot, list_reconciliation_records, list_snapshot_metadata
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations

NOW = datetime(2026, 5, 21, tzinfo=UTC)


def db():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute("INSERT INTO platforms(platform_id, name, platform_type, default_currency, created_at) VALUES ('plat_test', 'Synthetic Bank', 'bank', 'CHF', '2026-01-01')")
    conn.execute("INSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, is_active, created_at) VALUES ('acct_test', 'plat_test', 'Testkonto', 'cash', 'CHF', 1, '2026-01-01')")
    conn.commit()
    return conn


def add_snapshot(conn, snapshot_type: str, date: str, amount: str, currency: str = "CHF") -> None:
    conn.execute(
        """INSERT INTO cash_account_snapshots(snapshot_id, account_id, snapshot_type, balance_date, amount_original, currency, amount_chf, source, created_at)
           VALUES (?, 'acct_test', ?, ?, ?, ?, ?, 'synthetic', '2026-05-21T10:00:00Z')""",
        (f"snap_{snapshot_type}_{date}_{currency}", snapshot_type, date, amount, currency, amount),
    )
    conn.commit()


def record(conn):
    return list_reconciliation_records(conn, now=NOW)[0]


def test_identical_comparable_balances_are_reconciled_without_a_write():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "100.00")
    add_snapshot(conn, "manual_balance", "2026-05-20", "100.00")

    item = record(conn)

    assert item.status == "reconciled"
    assert item.difference == "0.00"
    assert item.ledger_balance == "100.00"
    assert item.snapshot_balance == "100.00"
    assert item.freshness_status == "fresh"
    assert conn.execute("SELECT COUNT(*) FROM transactions").fetchone()[0] == 0


def test_difference_uses_ledger_minus_snapshot_with_decimal_semantics():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "100.10")
    add_snapshot(conn, "manual_balance", "2026-05-20", "99.90")

    item = record(conn)

    assert item.status == "difference"
    assert item.difference == "0.20"
    assert Decimal(item.difference) == Decimal("100.10") - Decimal("99.90")


def test_missing_comparison_balance_is_partial_not_zero():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "100.00")

    item = record(conn)

    assert item.status == "partial"
    assert item.snapshot_balance is None
    assert item.difference is None
    assert "comparison_snapshot_missing" in item.reason_codes


def test_currency_or_as_of_mismatch_is_never_subtracted():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "100.00")
    add_snapshot(conn, "manual_balance", "2026-05-20", "100.00", currency="USD")
    assert record(conn).status == "not_comparable"
    assert record(conn).difference is None

    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-19", "100.00")
    add_snapshot(conn, "manual_balance", "2026-05-20", "100.00")
    assert record(conn).status == "not_comparable"
    assert "as_of_mismatch" in record(conn).reason_codes


def test_stale_and_unavailable_states_use_source_as_of_not_request_time():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-01-01", "100.00")
    add_snapshot(conn, "manual_balance", "2026-01-01", "100.00")
    assert record(conn).status == "stale"
    assert record(conn).freshness_status == "stale"

    conn = db()
    item = record(conn)
    assert item.status == "unavailable"
    assert item.ledger_balance is None and item.snapshot_balance is None


def test_snapshot_and_fx_metadata_keep_actual_as_of_and_decimal_strings():
    conn = db()
    conn.execute("INSERT INTO account_value_snapshots(snapshot_id, account_id, valuation_date, total_value_chf, currency, source_type, quality_status, created_at) VALUES ('value_1', 'acct_test', '2026-05-20', '12.34', 'CHF', 'manual_total_value', 'ok', '2026-05-21T10:00:00Z')")
    conn.execute("INSERT INTO fx_rates(fx_rate_id, base_currency, quote_currency, rate_date, rate, provider, rate_type, quality_status, created_at) VALUES ('fx_1', 'USD', 'CHF', '2026-05-20', '0.91234567', 'synthetic_fx', 'close', 'fresh', '2026-05-21T10:00:00Z')")
    conn.commit()

    rows = list_snapshot_metadata(conn, now=NOW)

    valuation = next(row for row in rows if row.snapshot_kind == "account_valuation")
    fx = next(row for row in rows if row.snapshot_kind == "fx_rate")
    assert valuation.as_of == "2026-05-20" and valuation.received_at == "2026-05-21T10:00:00Z"
    assert valuation.value == "12.34"
    assert fx.base_currency == "USD" and fx.quote_currency == "CHF" and fx.value == "0.91234567"
    assert Decimal(fx.value) == Decimal("0.91234567")


def test_ledger_anchor_currency_and_subunit_difference_are_never_hidden():
    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "100.00", currency="USD")
    add_snapshot(conn, "manual_balance", "2026-05-20", "100.00", currency="CHF")
    item = record(conn)
    assert item.status == "not_comparable"
    assert item.difference is None

    conn = db()
    add_snapshot(conn, "csv_anchor_balance", "2026-05-20", "1.234", currency="CHF")
    add_snapshot(conn, "manual_balance", "2026-05-20", "1.230", currency="CHF")
    item = record(conn)
    assert item.status == "difference"
    assert item.difference == "0.004"


def test_mixed_stale_and_missing_source_is_partial():
    assert combined_freshness(["stale", "unavailable"]) == "partial"


def test_api_redacts_adversarial_account_labels_ids_and_sources():
    conn = db()
    conn.execute("UPDATE accounts SET account_name='CH9300762011623852957', account_id='acct_secret' WHERE account_id='acct_test'")
    conn.execute("UPDATE cash_account_snapshots SET account_id='acct_secret' WHERE account_id='acct_test'")
    conn.execute("INSERT INTO cash_account_snapshots(snapshot_id, account_id, snapshot_type, balance_date, amount_original, currency, amount_chf, source, created_at) VALUES ('anchor', 'acct_secret', 'csv_anchor_balance', '2026-05-20', '1.00', 'CHF', '1.00', 'local', '2026-05-20T12:00:00Z')")
    conn.execute("INSERT INTO cash_account_snapshots(snapshot_id, account_id, snapshot_type, balance_date, amount_original, currency, amount_chf, source, created_at) VALUES ('manual', 'acct_secret', 'manual_balance', '2026-05-20', '1.00', 'CHF', '1.00', 'local', '2026-05-20T12:00:00Z')")
    conn.execute("INSERT INTO account_value_snapshots(snapshot_id, account_id, valuation_date, total_value_chf, currency, source_type, quality_status, created_at) VALUES ('value_sensitive', 'acct_secret', '2026-05-20', '1.00', 'CHF', '/tmp/secret.csv', 'ok', '2026-05-20T12:00:00Z')")
    conn.commit()

    app = create_app(write_mode="disabled")
    app.dependency_overrides[get_db] = lambda: conn
    response = TestClient(app).get("/api/portfolio/reconciliation-snapshot")
    assert response.status_code == 200
    rendered = response.text
    assert "CH9300762011623852957" not in rendered and "acct_secret" not in rendered and "/tmp/secret.csv" not in rendered
    assert "account-" in rendered and "Konto (redigiert)" in rendered and '"source":"local"' in rendered


def test_read_only_api_and_openapi_contract_expose_no_sensitive_account_fields():
    app = create_app(write_mode="disabled")
    client = TestClient(app)
    schema = client.get("/openapi.json").json()
    operations = schema["paths"]["/api/portfolio/reconciliation-snapshot"]

    assert set(operations) == {"get"}
    response = client.get("/api/portfolio/reconciliation-snapshot")
    assert response.status_code == 200
    rendered = response.text.lower()
    assert "iban" not in rendered and "wallet_address" not in rendered and "source_filename" not in rendered
    assert build_reconciliation_snapshot(db(), now=NOW)["data_quality_status"] == "unavailable"
