from __future__ import annotations

from collections.abc import Iterator
from decimal import Decimal
import sqlite3
from sqlite3 import Connection

from fastapi.testclient import TestClient

from jarvis_finance.api.dependencies import get_db
from jarvis_finance.api.main import create_app
from jarvis_finance.storage.migrations import apply_migrations


def _connect_threadsafe_memory() -> Connection:
    conn = sqlite3.connect(":memory:", check_same_thread=False)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA foreign_keys = ON")
    return conn


def _client(conn: Connection) -> TestClient:
    app = create_app()

    def override_db() -> Iterator[Connection]:
        yield conn

    app.dependency_overrides[get_db] = override_db
    return TestClient(app)


def _seed_minimal_read_models(conn: Connection) -> None:
    now = "2099-05-17T12:00:00Z"
    conn.execute("INSERT INTO platforms(platform_id, name, platform_type, country, default_currency, created_at) VALUES ('p1', 'Demo Bank', 'broker', 'CH', 'CHF', ?)", (now,))
    conn.execute("INSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, created_at) VALUES ('a1', 'p1', 'Trading', 'brokerage', 'CHF', ?)", (now,))
    conn.execute("INSERT INTO instruments(instrument_id, asset_class, name, ticker, isin, currency, created_at) VALUES ('i1', 'stock', 'Demo Equity', 'DEMO', 'CH0000000000', 'CHF', ?)", (now,))
    conn.execute(
        """
        INSERT INTO transactions(transaction_id, transaction_type, account_id, instrument_id, trade_date, quantity, price_original, gross_amount_original, net_amount_original, currency_original, fx_rate_to_chf, fx_status, gross_amount_chf, net_amount_chf, source_type, is_confirmed, quality_status, created_at)
        VALUES ('t1', 'initial_snapshot', 'a1', 'i1', '2026-05-16', '2', '10', '20', '20', 'CHF', '1', 'ok', '20', '20', 'fixture', 1, 'ok', ?)
        """,
        (now,),
    )
    conn.execute("INSERT INTO market_prices(market_price_id, instrument_id, price_date, close, currency, provider, quality_status, created_at) VALUES ('mp1', 'i1', '2026-05-16', '12.34', 'CHF', 'fixture', 'fresh', ?)", (now,))
    conn.execute("INSERT INTO cash_balances(cash_balance_id, account_id, balance_date, currency, amount_original, fx_rate_to_chf, amount_chf, source_type, quality_status, created_at) VALUES ('c1', 'a1', '2026-05-16', 'CHF', '100.25', '1', '100.25', 'fixture', 'ok', ?)", (now,))
    conn.execute("INSERT INTO crypto_wallets(wallet_id, wallet_name, wallet_type, created_at) VALUES ('w1', 'Cold Wallet', 'hardware', ?)", (now,))
    conn.execute("INSERT INTO crypto_assets(asset_id, symbol, coin_name, coingecko_id, created_at) VALUES ('btc', 'BTC', 'Bitcoin', 'bitcoin', ?)", (now,))
    conn.execute("INSERT INTO crypto_holdings(crypto_holding_id, wallet_id, asset_id, quantity, verification_status, created_at) VALUES ('h1', 'w1', 'btc', '0.5', 'ok', ?)", (now,))
    conn.execute("INSERT INTO crypto_prices(crypto_price_id, asset_id, coingecko_id, price_currency, price, provider, provider_timestamp, fetched_at, quality_status) VALUES ('cp1', 'btc', 'bitcoin', 'CHF', '80000', 'fixture', ?, ?, 'fresh')", (now, now))
    conn.commit()


def _assert_overview_matches_synthetic_fixture(payload: dict) -> None:
    expected_crypto = Decimal("0.5") * Decimal("80000")
    expected_equity = Decimal("2") * Decimal("10")
    expected_total = expected_crypto + expected_equity

    assert Decimal(payload["crypto_value_chf"]) == expected_crypto
    assert Decimal(payload["postfinance_equity_value_chf"]) == expected_equity
    assert Decimal(payload["truewealth_value_chf"]) == Decimal("0")
    assert Decimal(payload["cash_value_chf"]) == Decimal("0")
    assert Decimal(payload["total_value_chf"]) == expected_total


def test_fastapi_v0_health_and_read_only_contracts() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    client = _client(conn)

    response = client.get("/api/health")

    assert response.status_code == 200
    assert response.json() == {"status": "ok", "app": "jarvis-finance-api", "api_version": "0", "mode": "read-only"}
    assert client.post("/api/crypto/price-update").status_code == 404


def test_fastapi_v0_overview_and_positions_return_decimal_strings() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    _seed_minimal_read_models(conn)
    client = _client(conn)

    overview = client.get("/api/overview").json()
    crypto = client.get("/api/crypto/positions").json()
    equity = client.get("/api/equity/positions").json()
    cash = client.get("/api/cash/summary").json()

    _assert_overview_matches_synthetic_fixture(overview)
    assert isinstance(overview["total_value_chf"], str)
    assert crypto[0]["quantity_total"] == "0.5"
    assert isinstance(crypto[0]["market_value_chf"], str)
    assert equity[0]["name"] == "Demo Equity"
    assert isinstance(equity[0]["price"], str)
    assert cash["cash_chf"] == "0.00"
    assert cash["positions"] == []


def test_fastapi_v0_provider_status_is_sanitized() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    client = _client(conn)

    payload = client.get("/api/provider/status").json()
    rendered = str(payload).lower()

    assert payload
    assert "api_key" not in rendered
    assert "token" not in rendered
    assert "secret" not in rendered



def test_vue_slice_overview_contract_uses_flat_decimal_strings() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    _seed_minimal_read_models(conn)
    client = _client(conn)

    payload = client.get("/api/overview").json()

    assert set(payload) == {
        "total_value_chf",
        "crypto_value_chf",
        "equity_value_chf",
        "postfinance_equity_value_chf",
        "truewealth_value_chf",
        "cash_value_chf",
        "unpriced_positions_count",
        "critical_alerts_count",
        "last_price_update",
        "data_quality_status",
    }
    _assert_overview_matches_synthetic_fixture(payload)
    assert isinstance(payload["crypto_value_chf"], str)
    assert payload["data_quality_status"] in {"ok", "warning", "critical"}


def test_vue_slice_crypto_contract_and_detail_endpoint() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    _seed_minimal_read_models(conn)
    client = _client(conn)

    positions = client.get("/api/crypto/positions").json()
    detail = client.get("/api/crypto/positions/btc").json()

    assert positions[0] == {
        "asset_id": "btc",
        "name": "Bitcoin",
        "symbol": "BTC",
        "quantity_total": "0.5",
        "price_chf": "80000",
        "market_value_chf": "40000.00",
        "portfolio_share_pct": "100.00",
        "wallet_count": 1,
        "price_status": "Aktuell",
        "last_price_update": "2099-05-17T12:00:00Z",
        "coingecko_url": "https://www.coingecko.com/en/coins/bitcoin",
    }
    assert detail["asset_id"] == "btc"
    assert detail["wallet_allocations"][0]["wallet"] == "Cold Wallet"
    assert isinstance(detail["wallet_allocations"][0]["quantity"], str)


def test_vue_slice_equity_contract_and_detail_endpoint() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    _seed_minimal_read_models(conn)
    client = _client(conn)

    positions = client.get("/api/equity/positions").json()
    detail = client.get("/api/equity/positions/a1:i1").json()

    expected = {
        "position_id": "a1:i1",
        "name": "Demo Equity",
        "ticker": "DEMO",
        "isin": "CH0000000000",
        "account": "Trading",
        "asset_class": "Aktie",
        "quantity": "0",
        "currency": "CHF",
        "price": "12.34",
        "market_value_chf": None,
        "status": "Einstand unvollständig",
    }
    assert {key: positions[0][key] for key in expected} == expected
    assert positions[0]["data_status"] == "missing"
    assert positions[0]["status_code"] == "price_missing"
    assert positions[0]["portfolio_share_pct"] is None
    assert detail["position_id"] == "a1:i1"


def test_vue_slice_wallets_contract() -> None:
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    _seed_minimal_read_models(conn)
    client = _client(conn)

    wallets = client.get("/api/wallets").json()

    assert wallets == [
        {
            "wallet_id": "w1",
            "name": "Cold Wallet",
            "wallet_type": "hardware",
            "provider": "hardware",
            "coin_count": 1,
            "market_value_chf": "40000.00",
            "status": "Aktuell",
        }
    ]
    detail = client.get("/api/wallets/w1").json()
    assert detail["coins"][0]["symbol"] == "BTC"
    assert isinstance(detail["coins"][0]["quantity"], str)


def test_vue_slice_reports_contract_and_no_secrets(tmp_path, monkeypatch) -> None:
    runtime = tmp_path / "runtime"
    reports = runtime / "reports"
    reports.mkdir(parents=True)
    (reports / "portfolio_report.html").write_text("<html>ok</html>", encoding="utf-8")
    monkeypatch.setenv("JARVIS_FINANCE_RUNTIME_DIR", str(runtime))
    monkeypatch.setenv("JARVIS_FINANCE_BLOCK_RUNTIME_INSIDE_REPO", "0")
    monkeypatch.chdir(tmp_path)
    conn = _connect_threadsafe_memory()
    apply_migrations(conn)
    client = _client(conn)

    payload = client.get("/api/reports").json()
    rendered = str(payload).lower()

    assert payload[0]["format"] == "html"
    assert payload[0]["path_display"].endswith("portfolio_report.html")
    assert "api_key" not in rendered
    assert "token" not in rendered
    assert "secret" not in rendered
