"""Tests for the GUI live-balances fetcher (soft-error handling)."""

from __future__ import annotations

from decimal import Decimal
from typing import Any

import pytest

from cerbero_bite.clients.deribit import DeribitClient
from cerbero_bite.gui.live_data import _fetch_deribit_currency


class _FakeDeribit:
    def __init__(self, payload: dict[str, Any] | Exception) -> None:
        self._payload = payload

    async def get_account_summary(self, currency: str) -> dict[str, Any]:
        del currency  # not used by the fake; kept for signature parity
        if isinstance(self._payload, Exception):
            raise self._payload
        return self._payload


@pytest.mark.asyncio
async def test_soft_error_payload_becomes_row_error() -> None:
    """MCP V2 returns 200 + ``error`` field when upstream auth fails."""
    fake = _FakeDeribit(
        {
            "equity": 0,
            "balance": 0,
            "available_funds": 0,
            "unrealized_pnl": 0,
            "error": "Deribit auth failed (code=13004): invalid_credentials",
        }
    )
    row = await _fetch_deribit_currency(
        deribit=fake,  # type: ignore[arg-type]
        currency="USDC",
    )
    assert row.exchange == "deribit"
    assert row.currency == "USDC"
    assert row.equity is None
    assert row.available is None
    assert row.unrealized_pnl is None
    assert row.error is not None
    assert "invalid_credentials" in row.error


@pytest.mark.asyncio
async def test_clean_payload_populates_balance_fields() -> None:
    fake = _FakeDeribit(
        {
            "equity": "12.5",
            "available_funds": "10.0",
            "unrealized_pnl": "-0.25",
        }
    )
    row = await _fetch_deribit_currency(
        deribit=fake,  # type: ignore[arg-type]
        currency="USDC",
    )
    assert row.error is None
    assert row.equity == Decimal("12.5")
    assert row.available == Decimal("10.0")
    assert row.unrealized_pnl == Decimal("-0.25")


@pytest.mark.asyncio
async def test_exception_becomes_row_error() -> None:
    fake = _FakeDeribit(RuntimeError("boom"))
    row = await _fetch_deribit_currency(
        deribit=fake,  # type: ignore[arg-type]
        currency="USDC",
    )
    assert row.equity is None
    assert row.error is not None
    assert "RuntimeError" in row.error
    assert "boom" in row.error


@pytest.mark.asyncio
async def test_blank_error_field_is_ignored() -> None:
    """An ``error`` field that is empty/None must not trigger the soft-error path."""
    fake = _FakeDeribit(
        {"equity": "1.0", "available_funds": "1.0", "unrealized_pnl": "0.0", "error": None}
    )
    row = await _fetch_deribit_currency(
        deribit=fake,  # type: ignore[arg-type]
        currency="USDC",
    )
    assert row.error is None
    assert row.equity == Decimal("1.0")


# Sanity-check: the production class signature is what we expect to be drop-in
# replaceable by ``_FakeDeribit``.
def test_fake_matches_production_signature() -> None:
    assert hasattr(DeribitClient, "get_account_summary")
