"""Tests for trading API endpoints."""

import pytest
from decimal import Decimal
from unittest.mock import Mock, patch, MagicMock
from fastapi.testclient import TestClient
from datetime import datetime

from backend.main import app
from backend.core.schemas import OrderCreate, OrderSide, OrderType
from src.core.database import Order, OrderStatus


@pytest.fixture
def client():
    """Test client fixture."""
    return TestClient(app)


@pytest.fixture
def mock_trading_engine():
    """Mock trading engine."""
    engine = Mock()
    engine.order_manager = Mock()
    return engine


@pytest.fixture
def mock_order():
    """Mock order object."""
    order = Mock()
    order.id = 1
    order.exchange_id = 1
    order.strategy_id = None
    order.symbol = "BTC/USD"
    order.order_type = OrderType.MARKET
    order.side = OrderSide.BUY
    order.status = OrderStatus.FILLED
    order.quantity = Decimal("0.1")
    order.price = Decimal("50000")
    order.filled_quantity = Decimal("0.1")
    order.average_fill_price = Decimal("50000")
    order.fee = Decimal("5")
    order.paper_trading = True
    order.created_at = datetime.now()
    order.updated_at = datetime.now()
    order.filled_at = datetime.now()
    return order


class TestCreateOrder:
    """Tests for POST /api/trading/orders."""
    
    @patch('backend.api.trading.get_trading_engine')
    def test_create_order_success(self, mock_get_engine, client, mock_trading_engine, mock_order):
        """Test successful order creation."""
        mock_get_engine.return_value = mock_trading_engine
        mock_trading_engine.execute_order.return_value = mock_order
        
        order_data = {
            "exchange_id": 1,
            "symbol": "BTC/USD",
            "side": "buy",
            "order_type": "market",
            "quantity": "0.1",
            "paper_trading": True
        }
        
        response = client.post("/api/trading/orders", json=order_data)
        
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == 1
        assert data["symbol"] == "BTC/USD"
        assert data["side"] == "buy"
        mock_trading_engine.execute_order.assert_called_once()
    
    @patch('backend.api.trading.get_trading_engine')
    def test_create_order_execution_failed(self, mock_get_engine, client, mock_trading_engine):
        """Test order creation when execution fails."""
        mock_get_engine.return_value = mock_trading_engine
        mock_trading_engine.execute_order.return_value = None
        
        order_data = {
            "exchange_id": 1,
            "symbol": "BTC/USD",
            "side": "buy",
            "order_type": "market",
            "quantity": "0.1",
            "paper_trading": True
        }
        
        response = client.post("/api/trading/orders", json=order_data)
        
        assert response.status_code == 400
        assert "Order execution failed" in response.json()["detail"]
    
    @patch('backend.api.trading.get_trading_engine')
    def test_create_order_invalid_data(self, client):
        """Test order creation with invalid data."""
        order_data = {
            "exchange_id": "invalid",
            "symbol": "BTC/USD",
            "side": "buy",
            "order_type": "market",
            "quantity": "0.1"
        }
        
        response = client.post("/api/trading/orders", json=order_data)
        
        assert response.status_code == 422  # Validation error


class TestGetOrders:
    """Tests for GET /api/trading/orders."""
    
    @patch('backend.api.trading.get_db')
    def test_get_orders_success(self, mock_get_db, client, mock_database):
        """Test getting orders."""
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        # Create mock orders
        mock_order1 = Mock(spec=Order)
        mock_order1.id = 1
        mock_order1.symbol = "BTC/USD"
        mock_order1.paper_trading = True
        
        mock_order2 = Mock(spec=Order)
        mock_order2.id = 2
        mock_order2.symbol = "ETH/USD"
        mock_order2.paper_trading = True
        
        mock_session.query.return_value.filter_by.return_value.order_by.return_value.limit.return_value.all.return_value = [mock_order1, mock_order2]
        
        response = client.get("/api/trading/orders?paper_trading=true&limit=10")
        
        assert response.status_code == 200
        data = response.json()
        assert isinstance(data, list)
        assert len(data) == 2


class TestGetOrder:
    """Tests for GET /api/trading/orders/{order_id}."""
    
    @patch('backend.api.trading.get_db')
    def test_get_order_success(self, mock_get_db, client, mock_database):
        """Test getting order by ID."""
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        mock_order = Mock(spec=Order)
        mock_order.id = 1
        mock_order.symbol = "BTC/USD"
        
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_order
        
        response = client.get("/api/trading/orders/1")
        
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == 1
    
    @patch('backend.api.trading.get_db')
    def test_get_order_not_found(self, mock_get_db, client):
        """Test getting non-existent order."""
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        mock_session.query.return_value.filter_by.return_value.first.return_value = None
        
        response = client.get("/api/trading/orders/999")
        
        assert response.status_code == 404
        assert "Order not found" in response.json()["detail"]


class TestCancelOrder:
    """Tests for POST /api/trading/orders/{order_id}/cancel."""
    
    @patch('backend.api.trading.get_trading_engine')
    @patch('backend.api.trading.get_db')
    def test_cancel_order_success(self, mock_get_db, mock_get_engine, client, mock_trading_engine):
        """Test successful order cancellation."""
        mock_get_engine.return_value = mock_trading_engine
        mock_trading_engine.order_manager.cancel_order.return_value = True
        
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        mock_order = Mock(spec=Order)
        mock_order.id = 1
        mock_order.status = OrderStatus.OPEN
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_order
        
        response = client.post("/api/trading/orders/1/cancel")
        
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "cancelled"
        assert data["order_id"] == 1
    
    @patch('backend.api.trading.get_db')
    def test_cancel_order_not_found(self, mock_get_db, client):
        """Test cancelling non-existent order."""
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        mock_session.query.return_value.filter_by.return_value.first.return_value = None
        
        response = client.post("/api/trading/orders/999/cancel")
        
        assert response.status_code == 404
        assert "Order not found" in response.json()["detail"]
    
    @patch('backend.api.trading.get_trading_engine')
    @patch('backend.api.trading.get_db')
    def test_cancel_order_already_filled(self, mock_get_db, mock_get_engine, client, mock_trading_engine):
        """Test cancelling already filled order."""
        mock_get_engine.return_value = mock_trading_engine
        
        mock_db = Mock()
        mock_session = Mock()
        mock_db.get_session.return_value = mock_session
        mock_get_db.return_value = mock_db
        
        mock_order = Mock(spec=Order)
        mock_order.id = 1
        mock_order.status = OrderStatus.FILLED
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_order
        
        response = client.post("/api/trading/orders/1/cancel")
        
        assert response.status_code == 400
        assert "cannot be cancelled" in response.json()["detail"]


class TestGetPositions:
    """Tests for GET /api/trading/positions."""
    
    @patch('backend.api.trading.get_paper_trading')
    def test_get_positions_paper_trading(self, mock_get_paper, client):
        """Test getting positions for paper trading."""
        mock_paper = Mock()
        mock_position = Mock()
        mock_position.symbol = "BTC/USD"
        mock_position.quantity = Decimal("0.1")
        mock_position.entry_price = Decimal("50000")
        mock_position.current_price = Decimal("51000")
        mock_position.unrealized_pnl = Decimal("100")
        mock_position.realized_pnl = Decimal("0")
        mock_paper.get_positions.return_value = [mock_position]
        mock_get_paper.return_value = mock_paper
        
        response = client.get("/api/trading/positions?paper_trading=true")
        
        assert response.status_code == 200
        data = response.json()
        assert isinstance(data, list)
        if len(data) > 0:
            assert data[0]["symbol"] == "BTC/USD"


class TestGetBalance:
    """Tests for GET /api/trading/balance."""
    
    @patch('backend.api.trading.get_paper_trading')
    def test_get_balance_paper_trading(self, mock_get_paper, client):
        """Test getting balance for paper trading."""
        mock_paper = Mock()
        mock_paper.get_balance.return_value = Decimal("1000")
        mock_paper.get_performance.return_value = {"total_return": 0.1}
        mock_get_paper.return_value = mock_paper
        
        response = client.get("/api/trading/balance?paper_trading=true")
        
        assert response.status_code == 200
        data = response.json()
        assert "balance" in data
        assert "performance" in data
        assert data["balance"] == 1000.0

