from __future__ import annotations

from decimal import Decimal

from jarvis_finance.crypto.assets import create_crypto_asset
from jarvis_finance.market.providers import PriceQuote, refresh_crypto_prices
from jarvis_finance.quality.alerts import create_alert, mute_alert, resolve_alert
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


class MockProvider:
    def __init__(self, quotes):
        self.quotes = list(quotes)
        self.calls = 0
    def get_crypto_price(self, coingecko_id: str, currency: str = 'CHF') -> PriceQuote:
        self.calls += 1
        return self.quotes.pop(0)


def conn():
    c = connect_memory(); apply_migrations(c); return c


def test_refresh_crypto_prices_stores_decimal_text_and_uses_cache() -> None:
    c = conn(); asset = create_crypto_asset(c, coin_name='Synthetic Bitcoin', symbol='BTC', coingecko_id='bitcoin')
    provider = MockProvider([PriceQuote('bitcoin','CHF',Decimal('12345.00000001'),provider_timestamp='1700000000')])
    ids = refresh_crypto_prices(c, provider=provider, currency='CHF')
    assert ids
    row = c.execute('SELECT price, typeof(price), quality_status FROM crypto_prices WHERE asset_id=?', (asset,)).fetchone()
    assert row['price'] == '12345.00000001'
    assert row['typeof(price)'] == 'text'
    assert row['quality_status'] == 'fresh'
    cached = refresh_crypto_prices(c, provider=provider, currency='CHF', max_age_seconds=3600)
    assert cached.written_price_ids == []
    assert cached.cached_count == 1
    assert provider.calls == 1


def test_refresh_crypto_prices_warns_missing_coingecko_and_missing_price_dedupes() -> None:
    c = conn(); missing = create_crypto_asset(c, coin_name='Synthetic Mystery', symbol='MYS')
    asset = create_crypto_asset(c, coin_name='Synthetic Ether', symbol='ETH', coingecko_id='ethereum')
    provider = MockProvider([PriceQuote('ethereum','CHF',None,quality_status='missing',error_message='no price')])
    refresh_crypto_prices(c, provider=provider, currency='CHF', max_age_seconds=0)
    refresh_crypto_prices(c, provider=MockProvider([PriceQuote('ethereum','CHF',None,quality_status='missing',error_message='no price')]), currency='CHF', max_age_seconds=0)
    assert c.execute("SELECT COUNT(*) AS c FROM alerts WHERE rule_id='missing_coingecko_id' AND entity_id=?", (missing,)).fetchone()['c'] == 1
    row = c.execute("SELECT COUNT(*) AS c, MAX(occurrence_count) AS occ FROM alerts WHERE rule_id='crypto_price_missing' AND entity_id=?", (asset,)).fetchone()
    assert row['c'] == 1
    assert row['occ'] >= 2


def test_alert_lifecycle_dedupes_resolved_and_muted_but_allows_new_fingerprint() -> None:
    c = conn()
    a1 = create_alert(c, priority='kritisch', category='fx', entity_type='transaction', entity_id='t1', rule_id='missing_fx', message='missing fx', evidence={'currency':'USD','date':'2026-01-01'})
    a2 = create_alert(c, priority='kritisch', category='fx', entity_type='transaction', entity_id='t1', rule_id='missing_fx', message='missing fx', evidence={'currency':'USD','date':'2026-01-01'})
    assert a1 == a2
    resolve_alert(c, a1)
    assert create_alert(c, priority='kritisch', category='fx', entity_type='transaction', entity_id='t1', rule_id='missing_fx', message='missing fx', evidence={'currency':'USD','date':'2026-01-01'}) == a1
    mute_alert(c, a1, muted_until='2099-01-01T00:00:00Z')
    assert create_alert(c, priority='kritisch', category='fx', entity_type='transaction', entity_id='t1', rule_id='missing_fx', message='missing fx', evidence={'currency':'USD','date':'2026-01-01'}) == a1
    a3 = create_alert(c, priority='kritisch', category='fx', entity_type='transaction', entity_id='t1', rule_id='missing_fx', message='missing fx', evidence={'currency':'EUR','date':'2026-01-01'})
    assert a3 != a1
