from __future__ import annotations

import urllib.error
from decimal import Decimal

from jarvis_finance.market.providers import CoinGeckoClient


class Resp:
    def __init__(self, payload: bytes): self.payload = payload
    def __enter__(self): return self
    def __exit__(self, *args): return False
    def read(self): return self.payload


def test_coingecko_client_retries_429_and_returns_quote_without_float() -> None:
    calls = {'n': 0}
    sleeps: list[float] = []
    def opener(url, timeout=20):
        calls['n'] += 1
        if calls['n'] < 3:
            raise urllib.error.HTTPError(url, 429, 'rate limited', None, None)
        return Resp(b'{"bitcoin":{"chf":0.00000001,"last_updated_at":1700000000}}')
    client = CoinGeckoClient(max_retries=3, initial_backoff_seconds=0.5, max_backoff_seconds=1.0, opener=opener, sleeper=sleeps.append)
    quote = client.get_crypto_price('bitcoin', 'CHF')
    assert calls['n'] == 3
    assert sleeps == [0.5, 1.0]
    assert quote.price == Decimal('1E-8')
    assert quote.quality_status == 'fresh'


def test_coingecko_client_429_after_retries_returns_stale_not_crash() -> None:
    def opener(url, timeout=20):
        raise urllib.error.HTTPError(url, 429, 'rate limited', None, None)
    client = CoinGeckoClient(max_retries=1, initial_backoff_seconds=0, opener=opener, sleeper=lambda s: None)
    quote = client.get_crypto_price('bitcoin', 'CHF')
    assert quote.price is None
    assert quote.quality_status == 'stale'
    assert '429' in (quote.error_message or '')


def test_coingecko_market_bundle_keeps_chf_and_usd_24h_data_in_one_cycle() -> None:
    urls: list[str] = []

    def opener(url, timeout=20):
        urls.append(url)
        if "/simple/price" in url:
            return Resp(b'{"bitcoin":{"chf":80000,"last_updated_at":1787904000},"tether":{"chf":0.79,"last_updated_at":1787904000}}')
        return Resp(b'[{"id":"bitcoin","current_price":100000,"high_24h":102000,"low_24h":98000,"price_change_percentage_24h":2.5,"last_updated":"2026-08-28T08:00:00Z"},{"id":"tether","current_price":0.98,"high_24h":0.99,"low_24h":0.97,"price_change_percentage_24h":-0.1,"last_updated":"2026-08-28T08:00:00Z"}]')

    bundle = CoinGeckoClient(max_retries=0, opener=opener).get_crypto_market_bundle(["bitcoin", "tether"])

    assert len(urls) == 2
    assert bundle["bitcoin"]["CHF"].price == Decimal("80000")
    assert bundle["bitcoin"]["USD"].price == Decimal("100000")
    assert bundle["bitcoin"]["USD"].high_24h == Decimal("102000")
    assert bundle["bitcoin"]["USD"].low_24h == Decimal("98000")
    assert bundle["bitcoin"]["USD"].change_24h_pct == Decimal("2.5")
