from __future__ import annotations

from decimal import Decimal

import pytest

from jarvis_finance.crypto.assets import create_crypto_asset, get_crypto_asset
from jarvis_finance.crypto.holdings import calculate_crypto_holdings, create_initial_holding_snapshot
from jarvis_finance.crypto.transactions import record_crypto_buy, record_crypto_sell, record_crypto_transfer
from jarvis_finance.crypto.wallets import create_wallet, deactivate_wallet, get_wallet, update_wallet
from jarvis_finance.storage.database import connect_memory
from jarvis_finance.storage.migrations import apply_migrations


def setup_conn():
    conn = connect_memory()
    apply_migrations(conn)
    conn.execute(
        "INSERT INTO platforms(platform_id, name, platform_type, default_currency, created_at) VALUES('p1','Synthetic Broker','Bank/Broker','CHF','now')"
    )
    conn.execute(
        "INSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, created_at) VALUES('a1','p1','Synthetic Cash','cash','CHF','now')"
    )
    conn.commit()
    return conn


def seed_wallets_assets(conn):
    w1 = create_wallet(conn, wallet_name='Synthetic Cold Wallet', wallet_type='Hardware Wallet')
    w2 = create_wallet(conn, wallet_name='Synthetic Exchange Wallet', wallet_type='Exchange')
    btc = create_crypto_asset(conn, coin_name='Synthetic Bitcoin', symbol='BTC', coingecko_id='bitcoin')
    eth = create_crypto_asset(conn, coin_name='Synthetic Ether', symbol='ETH', coingecko_id='ethereum')
    return w1, w2, btc, eth


def test_wallet_create_read_update_deactivate_and_duplicate_name_blocked() -> None:
    conn = setup_conn()
    wallet_id = create_wallet(conn, wallet_name='Synthetic Wallet Alpha', wallet_type='Hardware Wallet')
    wallet = get_wallet(conn, wallet_id)
    assert wallet['wallet_name'] == 'Synthetic Wallet Alpha'
    assert wallet['wallet_address'] is None

    with pytest.raises(ValueError, match='wallet_name'):
        create_wallet(conn, wallet_name='Synthetic Wallet Alpha', wallet_type='Software Wallet')
    with pytest.raises(ValueError, match='wallet_type'):
        create_wallet(conn, wallet_name='Synthetic Bad Type', wallet_type='Moon Bag')

    update_wallet(conn, wallet_id, wallet_type='DeFi', notes='synthetic update')
    assert get_wallet(conn, wallet_id)['wallet_type'] == 'DeFi'
    deactivate_wallet(conn, wallet_id, note='synthetic retire')
    assert get_wallet(conn, wallet_id)['is_active'] == 0


def test_crypto_asset_with_and_without_coingecko_and_symbol_conflict_warning() -> None:
    conn = setup_conn()
    a1 = create_crypto_asset(conn, coin_name='Synthetic Bitcoin', symbol='BTC', coingecko_id='bitcoin')
    assert get_crypto_asset(conn, a1)['coingecko_id'] == 'bitcoin'

    a2 = create_crypto_asset(conn, coin_name='Synthetic Bitcoin Wrapper', symbol='BTC', coingecko_id='wrapped-bitcoin')
    assert get_crypto_asset(conn, a2)['symbol'] == 'BTC'
    symbol_alerts = conn.execute("SELECT * FROM alerts WHERE rule_id='crypto_symbol_conflict'").fetchall()
    assert symbol_alerts

    a3 = create_crypto_asset(conn, coin_name='Synthetic Mystery Coin', symbol='MYST')
    assert get_crypto_asset(conn, a3)['coingecko_id'] is None
    missing_alerts = conn.execute("SELECT * FROM alerts WHERE rule_id='missing_coingecko_id'").fetchall()
    assert missing_alerts


def test_initial_crypto_holdings_multiple_wallets_aggregate_and_legacy_values_not_current() -> None:
    conn = setup_conn()
    w1, w2, btc, _ = seed_wallets_assets(conn)
    create_initial_holding_snapshot(
        conn, asset_id=btc, wallet_id=w1, quantity=Decimal('1.25'), verification_status='verified',
        last_verified_at='2026-01-01T00:00:00Z', legacy_snapshot_value_chf=Decimal('50000'), note='synthetic snapshot'
    )
    create_initial_holding_snapshot(
        conn, asset_id=btc, wallet_id=w2, quantity=Decimal('0.75'), verification_status='stale',
        legacy_snapshot_value_chf=Decimal('30000'), note='synthetic snapshot'
    )

    holdings = calculate_crypto_holdings(conn)
    assert holdings.wallet_holdings[(w1, btc)].quantity == Decimal('1.25')
    assert holdings.wallet_holdings[(w2, btc)].quantity == Decimal('0.75')
    assert holdings.total_by_asset[btc].quantity == Decimal('2.00')
    assert holdings.wallet_holdings[(w1, btc)].legacy_snapshot_value_chf == Decimal('50000')
    assert holdings.wallet_holdings[(w1, btc)].current_value_chf is None


def test_transfer_between_wallets_with_coin_fee_and_audit() -> None:
    conn = setup_conn()
    w1, w2, btc, _ = seed_wallets_assets(conn)
    create_initial_holding_snapshot(conn, asset_id=btc, wallet_id=w1, quantity=Decimal('2'), verification_status='verified', note='synthetic snapshot')

    txid = record_crypto_transfer(
        conn, asset_id=btc, from_wallet_id=w1, to_wallet_id=w2, quantity=Decimal('0.5'),
        fee_quantity=Decimal('0.01'), tx_hash='synthetic_tx_hash_001', note='synthetic transfer'
    )

    holdings = calculate_crypto_holdings(conn)
    assert holdings.wallet_holdings[(w1, btc)].quantity == Decimal('1.49')
    assert holdings.wallet_holdings[(w2, btc)].quantity == Decimal('0.5')
    assert conn.execute('SELECT transaction_type FROM crypto_transactions WHERE crypto_transaction_id=?', (txid,)).fetchone()['transaction_type'] == 'transfer'
    assert conn.execute("SELECT COUNT(*) AS c FROM audit_log WHERE action='crypto_transfer'").fetchone()['c'] == 1


def test_transfer_same_wallet_and_overdraft_are_rejected_or_critical() -> None:
    conn = setup_conn()
    w1, w2, btc, _ = seed_wallets_assets(conn)
    create_initial_holding_snapshot(conn, asset_id=btc, wallet_id=w1, quantity=Decimal('0.1'), verification_status='verified', note='synthetic snapshot')

    with pytest.raises(ValueError, match='different'):
        record_crypto_transfer(conn, asset_id=btc, from_wallet_id=w1, to_wallet_id=w1, quantity=Decimal('0.01'), note='bad synthetic transfer')
    with pytest.raises(ValueError, match='negative'):
        record_crypto_transfer(conn, asset_id=btc, from_wallet_id=w1, to_wallet_id=w2, quantity=Decimal('1'), note='overdraft synthetic transfer')
    alerts = conn.execute("SELECT * FROM alerts WHERE rule_id='crypto_negative_wallet_balance'").fetchall()
    assert alerts and alerts[0]['priority'] == 'kritisch'


def test_crypto_buy_with_fiat_creates_ledger_link_and_increases_wallet() -> None:
    conn = setup_conn()
    w1, _, btc, _ = seed_wallets_assets(conn)
    crypto_txid = record_crypto_buy(
        conn, account_id='a1', asset_id=btc, to_wallet_id=w1, quantity=Decimal('0.2'),
        gross_amount_original=Decimal('1000'), fee_original=Decimal('10'), currency='CHF', fx_rate_to_chf=Decimal('1'),
        note='synthetic buy'
    )
    row = conn.execute('SELECT transaction_id FROM crypto_transactions WHERE crypto_transaction_id=?', (crypto_txid,)).fetchone()
    assert row['transaction_id'] is not None
    ledger = conn.execute('SELECT transaction_type, source_id, net_amount_original FROM transactions WHERE transaction_id=?', (row['transaction_id'],)).fetchone()
    assert ledger['transaction_type'] == 'buy'
    assert ledger['source_id'] == crypto_txid
    assert Decimal(ledger['net_amount_original']) == Decimal('1000')
    assert calculate_crypto_holdings(conn).wallet_holdings[(w1, btc)].quantity == Decimal('0.2')


def test_crypto_sell_with_fiat_creates_ledger_link_and_reduces_wallet() -> None:
    conn = setup_conn()
    w1, _, btc, _ = seed_wallets_assets(conn)
    create_initial_holding_snapshot(conn, asset_id=btc, wallet_id=w1, quantity=Decimal('1'), verification_status='verified', note='synthetic snapshot')
    crypto_txid = record_crypto_sell(
        conn, account_id='a1', asset_id=btc, from_wallet_id=w1, quantity=Decimal('0.4'),
        gross_amount_original=Decimal('2000'), fee_original=Decimal('20'), currency='CHF', fx_rate_to_chf=Decimal('1'),
        note='synthetic sell'
    )
    linked = conn.execute('SELECT transaction_id FROM crypto_transactions WHERE crypto_transaction_id=?', (crypto_txid,)).fetchone()['transaction_id']
    ledger = conn.execute('SELECT transaction_type, net_amount_original FROM transactions WHERE transaction_id=?', (linked,)).fetchone()
    assert ledger['transaction_type'] == 'partial_sell'
    assert Decimal(ledger['net_amount_original']) == Decimal('1980')
    assert calculate_crypto_holdings(conn).wallet_holdings[(w1, btc)].quantity == Decimal('0.6')


def test_coin_fee_reduces_balance_and_fiat_fee_creates_ledger_fee_without_double_counting() -> None:
    conn = setup_conn()
    w1, w2, btc, _ = seed_wallets_assets(conn)
    create_initial_holding_snapshot(conn, asset_id=btc, wallet_id=w1, quantity=Decimal('1'), verification_status='verified', note='synthetic snapshot')
    record_crypto_transfer(conn, asset_id=btc, from_wallet_id=w1, to_wallet_id=w2, quantity=Decimal('0.2'), fee_quantity=Decimal('0.03'), note='synthetic coin fee')
    assert calculate_crypto_holdings(conn).wallet_holdings[(w1, btc)].quantity == Decimal('0.77')

    record_crypto_buy(
        conn, account_id='a1', asset_id=btc, to_wallet_id=w1, quantity=Decimal('0.1'),
        gross_amount_original=Decimal('500'), fee_original=Decimal('5'), fee_currency='CHF', currency='CHF', fx_rate_to_chf=Decimal('1'),
        note='synthetic fiat fee'
    )
    fee_rows = conn.execute("SELECT * FROM transactions WHERE transaction_type='fee' AND source_type='crypto'").fetchall()
    assert len(fee_rows) == 1
    assert Decimal(fee_rows[0]['gross_amount_original']) == Decimal('5')
    assert conn.execute("SELECT COUNT(*) AS c FROM audit_log WHERE action IN ('crypto_buy','crypto_transfer')").fetchone()['c'] >= 2


def test_crypto_buy_missing_fx_is_incomplete_and_fee_unclear_warns() -> None:
    conn = setup_conn()
    w1, _, btc, _ = seed_wallets_assets(conn)
    record_crypto_buy(
        conn, account_id='a1', asset_id=btc, to_wallet_id=w1, quantity=Decimal('0.1'),
        gross_amount_original=Decimal('100'), fee_original=Decimal('1'), fee_currency=None,
        currency='USD', fx_rate_to_chf=None, note='synthetic missing fx'
    )
    ledger = conn.execute("SELECT fx_status, quality_status FROM transactions WHERE source_type='crypto' AND transaction_type='buy'").fetchone()
    assert ledger['fx_status'] == 'missing'
    assert ledger['quality_status'] == 'incomplete'
    assert conn.execute("SELECT COUNT(*) AS c FROM alerts WHERE rule_id='crypto_fee_currency_unclear'").fetchone()['c'] == 1
