import unittest
from unittest.mock import MagicMock, patch
import ccxt
from datetime import datetime
import pandas as pd
from Exchange import Exchange


class TestExchange(unittest.TestCase):

    @patch('ccxt.binance')
    def setUp(self, mock_exchange):
        self.mock_client = MagicMock()
        mock_exchange.return_value = self.mock_client

        self.api_keys = {'key': 'test_key', 'secret': 'test_secret'}

        self.mock_client.fetch_markets.return_value = [
            {'symbol': 'BTC/USDT', 'active': True},
            {'symbol': 'ETH/USDT', 'active': True},
        ]
        self.mock_client.load_markets.return_value = {
            'BTC/USDT': {
                'limits': {
                    'amount': {'min': 0.001},
                    'cost': {'min': 10.0}  # Ensure the cost limit is included
                },
                'precision': {
                    'amount': 8,
                    'price': 8
                },
                'active': True,
                'symbol': 'BTC/USDT'
            },
            'ETH/USDT': {
                'limits': {
                    'amount': {'min': 0.01},
                    'cost': {'min': 20.0}  # Ensure the cost limit is included
                },
                'precision': {
                    'amount': 8,
                    'price': 8
                },
                'active': True,
                'symbol': 'ETH/USDT'
            }
        }
        self.mock_client.fetch_ticker.return_value = {'last': 30000.0}
        self.mock_client.fetch_ohlcv.return_value = [
            [1609459200000, 29000.0, 29500.0, 28800.0, 29400.0, 1000]
        ]
        self.mock_client.fetch_balance.return_value = {
            'total': {'BTC': 1.0, 'USDT': 1000.0}
        }
        self.mock_client.create_order.return_value = {
            'id': 'test_order_id',
            'symbol': 'BTC/USDT',
            'type': 'limit',
            'side': 'buy',
            'price': 30000.0,
            'amount': 1.0,
            'status': 'open'
        }
        self.mock_client.fetch_open_orders.return_value = [
            {'id': 'test_order_id', 'clientOrderId': None, 'symbol': 'BTC/USDT',
             'side': 'buy', 'type': 'limit', 'amount': 1.0, 'price': 30000.0,
             'status': 'open', 'filled': 0, 'remaining': 1.0, 'timestamp': None}
        ]
        self.mock_client.fetch_positions.return_value = [
            {'symbol': 'BTC/USDT', 'quantity': 1.0, 'entry_price': 29000.0}
        ]
        self.mock_client.fetch_order.return_value = {
            'id': 'test_order_id',
            'symbol': 'BTC/USDT',
            'side': 'buy',
            'amount': 1.0,
            'price': 30000.0,
            'status': 'open'
        }
        self.mock_client.timeframes = {
            '1m': '1m',
            '5m': '5m',
            '1h': '1h',
            '1d': '1d',
        }

        self.exchange = Exchange(name='Binance', api_keys=self.api_keys, exchange_id='binance')

    def test_connect_exchange(self):
        self.assertIsInstance(self.exchange.client, MagicMock)
        self.mock_client.load_markets.assert_called_once()
        self.assertEqual(self.exchange.symbols, ['BTC/USDT', 'ETH/USDT'])
        self.assertIn('BTC/USDT', self.exchange.exchange_info)
        self.assertIn('ETH/USDT', self.exchange.exchange_info)
        self.assertEqual(self.exchange.balances, [
            {'asset': 'BTC', 'balance': 1.0, 'pnl': 0},
            {'asset': 'USDT', 'balance': 1000.0, 'pnl': 0}
        ])

    def test_get_symbols(self):
        symbols = self.exchange.get_symbols()
        self.assertEqual(symbols, ['BTC/USDT', 'ETH/USDT'])

    def test_get_price(self):
        price = self.exchange.get_price('BTC/USDT')
        self.assertEqual(price, 30000.0)
        self.mock_client.fetch_ticker.assert_called_with('BTC/USDT')

    def test_get_balances(self):
        balances = self.exchange.get_balances()
        self.assertEqual(balances, [
            {'asset': 'BTC', 'balance': 1.0, 'pnl': 0},
            {'asset': 'USDT', 'balance': 1000.0, 'pnl': 0}
        ])
        self.mock_client.fetch_balance.assert_called_once()

    def test_get_min_qty(self):
        min_qty = self.exchange.get_min_qty('BTC/USDT')
        self.assertEqual(min_qty, 0.001)

    def test_get_min_notional_qty(self):
        min_notional_qty = self.exchange.get_min_notional_qty('BTC/USDT')
        self.assertEqual(min_notional_qty, 10.0)

    def test_get_order(self):
        order = self.exchange.get_order('BTC/USDT', 'test_order_id')
        self.assertIsInstance(order, dict)
        self.assertEqual(order['id'], 'test_order_id')
        self.mock_client.fetch_order.assert_called_with('test_order_id', 'BTC/USDT')

    def test_place_order(self):
        result, order = self.exchange.place_order(
            symbol='BTC/USDT',
            side='buy',
            type='limit',
            timeInForce='GTC',
            quantity=1.0,
            price=30000.0
        )
        self.assertEqual(result, 'Success')
        self.assertIsInstance(order, dict)
        self.assertEqual(order.get('id'), 'test_order_id')
        self.mock_client.create_order.assert_called_once()

    def test_get_open_orders(self):
        open_orders = self.exchange.get_open_orders()
        # Check that orders are returned and contain expected fields
        self.assertEqual(len(open_orders), 1)
        order = open_orders[0]
        self.assertEqual(order['id'], 'test_order_id')
        self.assertEqual(order['symbol'], 'BTC/USDT')
        self.assertEqual(order['side'], 'buy')
        self.assertEqual(order['quantity'], 1.0)
        self.assertEqual(order['price'], 30000.0)
        self.mock_client.fetch_open_orders.assert_called()

    def test_get_active_trades(self):
        active_trades = self.exchange.get_active_trades()
        self.assertEqual(active_trades, [
            {'symbol': 'BTC/USDT', 'side': 'buy', 'quantity': 1.0, 'price': 29000.0}
        ])
        self.mock_client.fetch_positions.assert_called_once()

    @patch('ccxt.binance')
    def test_fetch_ticker_invalid_response(self, mock_exchange):
        self.mock_client.fetch_ticker.side_effect = ccxt.ExchangeError('Invalid response')
        price = self.exchange.get_price('BTC/USDT')
        self.assertEqual(price, 0.0)
        self.mock_client.fetch_ticker.assert_called_with('BTC/USDT')

    @patch('ccxt.kucoin')
    def test_connect_exchange_uses_password_for_passphrase(self, mock_kucoin):
        mock_client = MagicMock()
        mock_kucoin.return_value = mock_client
        mock_client.fetch_open_orders.return_value = []
        mock_client.load_markets.return_value = {}
        mock_client.fetch_markets.return_value = []
        mock_client.fetch_balance.return_value = {'total': {}}
        mock_client.timeframes = {}

        api_keys = {
            'key': 'kucoin_key',
            'secret': 'kucoin_secret',
            'passphrase': 'kucoin_passphrase'
        }
        Exchange(name='KuCoin', api_keys=api_keys, exchange_id='kucoin')

        self.assertTrue(mock_kucoin.called)
        config = mock_kucoin.call_args.args[0]
        self.assertEqual(config.get('apiKey'), 'kucoin_key')
        self.assertEqual(config.get('secret'), 'kucoin_secret')
        self.assertEqual(config.get('password'), 'kucoin_passphrase')


if __name__ == '__main__':
    unittest.main()
