from __future__ import annotations

from decimal import Decimal

from jarvis_finance.equity.manage import add_equity_initial_snapshot
from jarvis_finance.fx.rates import resolve_fx_rate_to_chf
from jarvis_finance.market_data.prices import store_market_price
from jarvis_finance.services.equity_service import _position_status, list_equity_positions
from jarvis_finance.services.market_service import refresh_equity_fx
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


class Provider:
    def __init__(self, name: str, rate: Decimal | None = None, error: Exception | None = None) -> None:
        self.name = name
        self.rate = rate
        self.error = error
        self.calls: list[tuple[str, str, str | None]] = []

    def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None):
        self.calls.append((base_currency, quote_currency, rate_date))
        if self.error:
            raise self.error
        return self.rate


def setup_conn():
    conn = connect_memory()
    apply_migrations(conn)
    return conn


def seed_account(conn) -> str:
    conn.execute("INSERT INTO platforms(platform_id, name, platform_type, country, default_currency, created_at) VALUES ('p1', 'PostFinance', 'broker', 'CH', 'CHF', '2026-01-01')")
    conn.execute("INSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, created_at) VALUES ('a1', 'p1', 'Depot', 'brokerage', 'CHF', '2026-01-01')")
    conn.commit()
    return 'a1'


def test_resolve_fx_uses_frankfurter_before_stale_cache_and_before_twelvedata() -> None:
    conn = setup_conn()
    conn.execute("INSERT INTO fx_rates(fx_rate_id, base_currency, quote_currency, rate_date, rate, provider, rate_type, quality_status, created_at) VALUES ('old', 'EUR', 'CHF', '2026-05-19', '0.9000', 'cache_fixture', 'close', 'fresh', '2026-05-19')")
    frankfurter = Provider('frankfurter', Decimal('0.9700'))
    twelvedata = Provider('twelvedata', Decimal('0.9600'))

    result = resolve_fx_rate_to_chf(conn, base_currency='EUR', rate_date='2026-05-20', providers=[frankfurter, twelvedata], persist=True)

    assert result.status == 'ok'
    assert result.source == 'frankfurter'
    assert result.rate == Decimal('0.9700')
    assert frankfurter.calls == [('EUR', 'CHF', '2026-05-20')]
    assert twelvedata.calls == []
    latest = conn.execute("SELECT rate, provider FROM fx_rates WHERE base_currency='EUR' AND quote_currency='CHF' ORDER BY rate_date DESC, created_at DESC LIMIT 1").fetchone()
    assert latest['provider'] == 'frankfurter'
    assert latest['rate'] == '0.9700'


def test_resolve_fx_uses_cache_after_frankfurter_failure_before_twelvedata_noise() -> None:
    conn = setup_conn()
    conn.execute("INSERT INTO fx_rates(fx_rate_id, base_currency, quote_currency, rate_date, rate, provider, rate_type, quality_status, created_at) VALUES ('cached', 'USD', 'CHF', '2026-05-20', '0.8800', 'frankfurter', 'close', 'fresh', '2026-05-20')")
    frankfurter = Provider('frankfurter', error=RuntimeError('frankfurter_provider_error'))
    twelvedata = Provider('twelvedata', error=RuntimeError('twelvedata_provider_error'))

    result = resolve_fx_rate_to_chf(conn, base_currency='USD', rate_date='2026-05-20', providers=[frankfurter, twelvedata], persist=True)

    assert result.status == 'ok'
    assert result.source == 'cache:frankfurter'
    assert result.rate == Decimal('0.8800')
    assert frankfurter.calls == [('USD', 'CHF', '2026-05-20')]
    assert twelvedata.calls == []
    assert result.warning is None


def test_chf_resolution_is_not_needed_with_rate_one() -> None:
    conn = setup_conn()
    result = resolve_fx_rate_to_chf(conn, base_currency='CHF', rate_date='2026-05-20')
    assert result.status == 'not_needed'
    assert result.rate == Decimal('1')
    assert result.source == 'not_needed'


def test_apple_eur_valuation_fx_recheck_gets_chf_market_value_via_frankfurter(monkeypatch) -> None:
    conn = setup_conn()
    account_id = seed_account(conn)
    snap = add_equity_initial_snapshot(
        conn,
        account_id=account_id,
        asset_class='stock',
        name='Apple Inc.',
        isin='US0378331005',
        ticker='APC',
        exchange='XETRA',
        currency='CHF',
        quantity_text='2',
        cost_basis_original_text='300',
        snapshot_date='2026-01-01',
        category='Core',
        note='synthetic Apple CHF cost basis, EUR valuation',
        confirm=True,
    )
    store_market_price(conn, instrument_id=snap.instrument_id, price_date='2026-05-20', close=Decimal('100'), currency='EUR', provider='fixture', provider_symbol='APC.DE', provider_market='XETRA', quality_status='fresh')
    conn.commit()

    class Frankfurter:
        name = 'frankfurter'
        def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None):
            assert (base_currency, quote_currency) == ('EUR', 'CHF')
            return Decimal('0.95')

    class TwelveData:
        name = 'twelvedata'
        def get_rate(self, *args, **kwargs):
            raise AssertionError('TwelveData must not be called when Frankfurter succeeds')

    monkeypatch.setattr('jarvis_finance.fx.providers.FrankfurterFxProvider', lambda: Frankfurter())
    monkeypatch.setattr('jarvis_finance.fx.providers.TwelveDataFxProvider', lambda: TwelveData())

    result = refresh_equity_fx(conn, snap.instrument_id)
    rows = list_equity_positions(conn)
    apple = next(row for row in rows if row.name == 'Apple Inc.')

    assert result['updated'] >= 1
    assert result['warnings'] == []
    assert apple.status == 'Bewertet'
    assert apple.market_value_chf == '190.00'


def test_apple_usd_valuation_fx_recheck_gets_chf_market_value_via_frankfurter(monkeypatch) -> None:
    conn = setup_conn()
    account_id = seed_account(conn)
    snap = add_equity_initial_snapshot(
        conn,
        account_id=account_id,
        asset_class='stock',
        name='Apple Inc.',
        isin='US0378331005',
        ticker='AAPL',
        exchange='NASDAQ',
        currency='CHF',
        quantity_text='2',
        cost_basis_original_text='300',
        snapshot_date='2026-01-01',
        category='Core',
        note='synthetic Apple CHF cost basis, USD valuation',
        confirm=True,
    )
    store_market_price(conn, instrument_id=snap.instrument_id, price_date='2026-05-20', close=Decimal('200'), currency='USD', provider='fixture', provider_symbol='AAPL', provider_market='NASDAQ', quality_status='fresh')
    conn.commit()

    class Frankfurter:
        name = 'frankfurter'
        def get_rate(self, base_currency: str, quote_currency: str, rate_date: str | None = None):
            assert (base_currency, quote_currency) == ('USD', 'CHF')
            return Decimal('0.90')

    monkeypatch.setattr('jarvis_finance.fx.providers.FrankfurterFxProvider', lambda: Frankfurter())

    result = refresh_equity_fx(conn, snap.instrument_id)
    apple = next(row for row in list_equity_positions(conn) if row.name == 'Apple Inc.')

    assert result['warnings'] == []
    assert apple.status == 'Bewertet'
    assert apple.market_value_chf == '360.00'


def test_valuation_status_does_not_show_fx_missing_when_market_value_chf_exists() -> None:
    row = {
        'price_status': 'ok',
        'fx_status': 'missing_fx',
        'market_value_chf': '100.00',
        'quality_warnings': 'missing_fx',
        'data_quality_status': 'warning',
        'valuation_status': 'warning',
    }
    assert _position_status(row) == 'Einstand unvollständig'

    row['market_value_chf'] = ''
    assert _position_status(row) == 'FX fehlt für Bewertung'
