/**
 * Paper Trading Service Unit Tests
 *
 * Tests for paper trading service including:
 * - Account creation and management
 * - Position opening and closing
 * - P&L calculations
 * - Account statistics
 */

import { mockDb, createMockQueryResult, resetDatabaseMocks } from '../../../../__tests__/mocks/database.mock';
import type { PaperAccount, PaperPosition } from '../paper-trading.service';

// Mock database
jest.mock('../../../../shared/database', () => ({
  db: mockDb,
}));

// Mock logger
jest.mock('../../../../shared/utils/logger', () => ({
  logger: {
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn(),
  },
}));

// Mock market service
const mockGetPrice = jest.fn();
jest.mock('../market.service', () => ({
  marketService: {
    getPrice: mockGetPrice,
  },
}));

// Import service after mocks
import { paperTradingService } from '../paper-trading.service';

describe('PaperTradingService', () => {
  beforeEach(() => {
    resetDatabaseMocks();
    mockGetPrice.mockReset();
  });

  describe('createAccount', () => {
    it('should create a new paper trading account with default values', async () => {
      const mockAccount: PaperAccount = {
        id: 'account-123',
        userId: 'user-123',
        name: 'My Trading Account',
        initialBalance: 10000,
        currentBalance: 10000,
        currency: 'USD',
        totalTrades: 0,
        winningTrades: 0,
        totalPnl: 0,
        maxDrawdown: 0,
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockAccount]));

      const result = await paperTradingService.createAccount('user-123', {
        name: 'My Trading Account',
      });

      expect(result).toEqual(mockAccount);
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('INSERT INTO trading.paper_trading_accounts'),
        expect.arrayContaining(['user-123', 'My Trading Account'])
      );
    });

    it('should create account with custom initial balance', async () => {
      const mockAccount: PaperAccount = {
        id: 'account-123',
        userId: 'user-123',
        name: 'High Stakes Account',
        initialBalance: 100000,
        currentBalance: 100000,
        currency: 'USD',
        totalTrades: 0,
        winningTrades: 0,
        totalPnl: 0,
        maxDrawdown: 0,
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockAccount]));

      const result = await paperTradingService.createAccount('user-123', {
        name: 'High Stakes Account',
        initialBalance: 100000,
      });

      expect(result.initialBalance).toBe(100000);
      expect(result.currentBalance).toBe(100000);
    });

    it('should handle database error during account creation', async () => {
      mockDb.query.mockRejectedValueOnce(new Error('Database connection failed'));

      await expect(
        paperTradingService.createAccount('user-123', { name: 'Test Account' })
      ).rejects.toThrow('Database connection failed');
    });
  });

  describe('getAccount', () => {
    it('should retrieve an existing account', async () => {
      const mockAccount: PaperAccount = {
        id: 'account-123',
        userId: 'user-123',
        name: 'My Account',
        initialBalance: 10000,
        currentBalance: 12500,
        currency: 'USD',
        totalTrades: 25,
        winningTrades: 18,
        totalPnl: 2500,
        maxDrawdown: -500,
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockAccount]));

      const result = await paperTradingService.getAccount('account-123');

      expect(result).toEqual(mockAccount);
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('SELECT * FROM trading.paper_trading_accounts'),
        ['account-123']
      );
    });

    it('should return null for non-existent account', async () => {
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.getAccount('non-existent');

      expect(result).toBeNull();
    });
  });

  describe('openPosition', () => {
    it('should open a long position with market price', async () => {
      mockGetPrice.mockResolvedValueOnce(50000);

      const mockPosition: PaperPosition = {
        id: 'position-123',
        accountId: 'account-123',
        userId: 'user-123',
        symbol: 'BTCUSDT',
        direction: 'long',
        lotSize: 0.1,
        entryPrice: 50000,
        stopLoss: 49000,
        takeProfit: 52000,
        status: 'open',
        openedAt: new Date(),
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockPosition]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.openPosition('account-123', 'user-123', {
        symbol: 'BTCUSDT',
        direction: 'long',
        lotSize: 0.1,
        stopLoss: 49000,
        takeProfit: 52000,
      });

      expect(result).toEqual(mockPosition);
      expect(mockGetPrice).toHaveBeenCalledWith('BTCUSDT');
      expect(result.entryPrice).toBe(50000);
    });

    it('should open a short position with specified price', async () => {
      const mockPosition: PaperPosition = {
        id: 'position-124',
        accountId: 'account-123',
        userId: 'user-123',
        symbol: 'ETHUSDT',
        direction: 'short',
        lotSize: 1.0,
        entryPrice: 3000,
        stopLoss: 3100,
        takeProfit: 2850,
        status: 'open',
        openedAt: new Date(),
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockPosition]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.openPosition('account-123', 'user-123', {
        symbol: 'ETHUSDT',
        direction: 'short',
        lotSize: 1.0,
        entryPrice: 3000,
        stopLoss: 3100,
        takeProfit: 2850,
      });

      expect(result.direction).toBe('short');
      expect(result.entryPrice).toBe(3000);
      expect(mockGetPrice).not.toHaveBeenCalled();
    });

    it('should handle insufficient balance error', async () => {
      mockGetPrice.mockResolvedValueOnce(50000);
      mockDb.query.mockRejectedValueOnce(
        new Error('Insufficient balance for position')
      );

      await expect(
        paperTradingService.openPosition('account-123', 'user-123', {
          symbol: 'BTCUSDT',
          direction: 'long',
          lotSize: 100,
        })
      ).rejects.toThrow('Insufficient balance for position');
    });
  });

  describe('closePosition', () => {
    it('should close position with profit at market price', async () => {
      mockGetPrice.mockResolvedValueOnce(52000);

      const mockClosedPosition: PaperPosition = {
        id: 'position-123',
        accountId: 'account-123',
        userId: 'user-123',
        symbol: 'BTCUSDT',
        direction: 'long',
        lotSize: 0.1,
        entryPrice: 50000,
        exitPrice: 52000,
        status: 'closed',
        openedAt: new Date(Date.now() - 3600000),
        closedAt: new Date(),
        closeReason: 'Manual close',
        realizedPnl: 200,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockClosedPosition]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.closePosition(
        'position-123',
        'account-123',
        { closeReason: 'Manual close' }
      );

      expect(result.status).toBe('closed');
      expect(result.exitPrice).toBe(52000);
      expect(result.realizedPnl).toBe(200);
      expect(mockGetPrice).toHaveBeenCalled();
    });

    it('should close position with loss at specified price', async () => {
      const mockClosedPosition: PaperPosition = {
        id: 'position-124',
        accountId: 'account-123',
        userId: 'user-123',
        symbol: 'ETHUSDT',
        direction: 'long',
        lotSize: 1.0,
        entryPrice: 3000,
        exitPrice: 2900,
        status: 'closed',
        openedAt: new Date(Date.now() - 3600000),
        closedAt: new Date(),
        closeReason: 'Stop loss triggered',
        realizedPnl: -100,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockClosedPosition]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.closePosition(
        'position-124',
        'account-123',
        { exitPrice: 2900, closeReason: 'Stop loss triggered' }
      );

      expect(result.exitPrice).toBe(2900);
      expect(result.realizedPnl).toBe(-100);
      expect(result.closeReason).toBe('Stop loss triggered');
    });

    it('should handle position not found error', async () => {
      mockDb.query.mockRejectedValueOnce(new Error('Position not found'));

      await expect(
        paperTradingService.closePosition('invalid-id', 'account-123', {})
      ).rejects.toThrow('Position not found');
    });
  });

  describe('getOpenPositions', () => {
    it('should retrieve all open positions for an account', async () => {
      const mockPositions: PaperPosition[] = [
        {
          id: 'pos-1',
          accountId: 'account-123',
          userId: 'user-123',
          symbol: 'BTCUSDT',
          direction: 'long',
          lotSize: 0.1,
          entryPrice: 50000,
          status: 'open',
          openedAt: new Date(),
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: 'pos-2',
          accountId: 'account-123',
          userId: 'user-123',
          symbol: 'ETHUSDT',
          direction: 'short',
          lotSize: 1.0,
          entryPrice: 3000,
          status: 'open',
          openedAt: new Date(),
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockDb.query.mockResolvedValueOnce(createMockQueryResult(mockPositions));

      const result = await paperTradingService.getOpenPositions('account-123');

      expect(result).toHaveLength(2);
      expect(result[0].status).toBe('open');
      expect(result[1].status).toBe('open');
    });

    it('should return empty array when no open positions', async () => {
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      const result = await paperTradingService.getOpenPositions('account-123');

      expect(result).toEqual([]);
    });
  });

  describe('getAccountSummary', () => {
    it('should calculate account summary with open positions', async () => {
      const mockAccount: PaperAccount = {
        id: 'account-123',
        userId: 'user-123',
        name: 'Test Account',
        initialBalance: 10000,
        currentBalance: 11500,
        currency: 'USD',
        totalTrades: 10,
        winningTrades: 7,
        totalPnl: 1500,
        maxDrawdown: -300,
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockAccount]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ count: 3 }]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ unrealized_pnl: 500 }]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ today_pnl: 150 }]));

      const result = await paperTradingService.getAccountSummary('account-123');

      expect(result.account).toEqual(mockAccount);
      expect(result.openPositions).toBe(3);
      expect(result.unrealizedPnl).toBe(500);
      expect(result.todayPnl).toBe(150);
      expect(result.winRate).toBe(70);
      expect(result.totalEquity).toBe(12000);
    });

    it('should handle account with no positions', async () => {
      const mockAccount: PaperAccount = {
        id: 'account-123',
        userId: 'user-123',
        name: 'Empty Account',
        initialBalance: 10000,
        currentBalance: 10000,
        currency: 'USD',
        totalTrades: 0,
        winningTrades: 0,
        totalPnl: 0,
        maxDrawdown: 0,
        isActive: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockDb.query.mockResolvedValueOnce(createMockQueryResult([mockAccount]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ count: 0 }]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ unrealized_pnl: 0 }]));
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([{ today_pnl: 0 }]));

      const result = await paperTradingService.getAccountSummary('account-123');

      expect(result.openPositions).toBe(0);
      expect(result.winRate).toBe(0);
      expect(result.totalEquity).toBe(10000);
    });
  });

  describe('getPositionHistory', () => {
    it('should retrieve closed positions history', async () => {
      const mockHistory: PaperPosition[] = [
        {
          id: 'pos-1',
          accountId: 'account-123',
          userId: 'user-123',
          symbol: 'BTCUSDT',
          direction: 'long',
          lotSize: 0.1,
          entryPrice: 50000,
          exitPrice: 51000,
          status: 'closed',
          openedAt: new Date(Date.now() - 86400000),
          closedAt: new Date(Date.now() - 43200000),
          realizedPnl: 100,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockDb.query.mockResolvedValueOnce(createMockQueryResult(mockHistory));

      const result = await paperTradingService.getPositionHistory('account-123', { limit: 10 });

      expect(result).toHaveLength(1);
      expect(result[0].status).toBe('closed');
      expect(result[0].realizedPnl).toBe(100);
    });

    it('should filter history by symbol', async () => {
      mockDb.query.mockResolvedValueOnce(createMockQueryResult([]));

      await paperTradingService.getPositionHistory('account-123', {
        symbol: 'ETHUSDT',
        limit: 10,
      });

      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('symbol = $2'),
        expect.arrayContaining(['account-123', 'ETHUSDT'])
      );
    });
  });
});
