/**
 * Portfolio Service Unit Tests
 *
 * Tests for portfolio service including:
 * - Portfolio creation and management
 * - Asset allocation and rebalancing
 * - Portfolio statistics and goals
 * - Performance tracking
 */

import { mockDb, createMockQueryResult, resetDatabaseMocks } from '../../../../__tests__/mocks/database.mock';

// Mock database (portfolio service uses in-memory storage, but may use DB in future)
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();
const mockGetPrices = jest.fn();
jest.mock('../../trading/services/market.service', () => ({
  marketService: {
    getPrice: mockGetPrice,
    getPrices: mockGetPrices,
  },
}));

// Import service after mocks
import { portfolioService } from '../portfolio.service';

describe('PortfolioService', () => {
  beforeEach(() => {
    resetDatabaseMocks();
    mockGetPrice.mockReset();
    mockGetPrices.mockReset();
    // Clear in-memory storage
    jest.clearAllMocks();
  });

  describe('createPortfolio', () => {
    it('should create conservative portfolio with default allocations', async () => {
      const result = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Conservative Portfolio',
        riskProfile: 'conservative',
      });

      expect(result.userId).toBe('user-123');
      expect(result.name).toBe('Conservative Portfolio');
      expect(result.riskProfile).toBe('conservative');
      expect(result.allocations).toBeDefined();
      expect(result.allocations.length).toBeGreaterThan(0);
    });

    it('should create moderate portfolio with balanced allocations', async () => {
      const result = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Moderate Portfolio',
        riskProfile: 'moderate',
      });

      expect(result.riskProfile).toBe('moderate');
      expect(result.allocations.length).toBeGreaterThanOrEqual(3);
    });

    it('should create aggressive portfolio with high-risk allocations', async () => {
      const result = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Aggressive Portfolio',
        riskProfile: 'aggressive',
      });

      expect(result.riskProfile).toBe('aggressive');
      expect(result.allocations).toBeDefined();
    });

    it('should create portfolio with custom allocations', async () => {
      const customAllocations = [
        { asset: 'BTC', targetPercent: 60 },
        { asset: 'ETH', targetPercent: 30 },
        { asset: 'USDT', targetPercent: 10 },
      ];

      const result = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Custom Portfolio',
        riskProfile: 'moderate',
        customAllocations,
      });

      expect(result.allocations.length).toBe(3);
      expect(result.allocations[0].asset).toBe('BTC');
      expect(result.allocations[0].targetPercent).toBe(60);
    });

    it('should validate total allocation equals 100%', async () => {
      const invalidAllocations = [
        { asset: 'BTC', targetPercent: 60 },
        { asset: 'ETH', targetPercent: 30 },
      ];

      await expect(
        portfolioService.createPortfolio({
          userId: 'user-123',
          name: 'Invalid Portfolio',
          riskProfile: 'moderate',
          customAllocations: invalidAllocations,
        })
      ).rejects.toThrow('Allocations must total 100%');
    });
  });

  describe('getUserPortfolios', () => {
    it('should retrieve all portfolios for a user', async () => {
      await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Portfolio 1',
        riskProfile: 'conservative',
      });

      await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Portfolio 2',
        riskProfile: 'aggressive',
      });

      const result = await portfolioService.getUserPortfolios('user-123');

      expect(result).toHaveLength(2);
      expect(result[0].userId).toBe('user-123');
      expect(result[1].userId).toBe('user-123');
    });

    it('should return empty array for user with no portfolios', async () => {
      const result = await portfolioService.getUserPortfolios('user-999');

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

  describe('getPortfolioById', () => {
    it('should retrieve a specific portfolio by ID', async () => {
      const created = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.getPortfolioById(created.id);

      expect(result).toBeDefined();
      expect(result?.id).toBe(created.id);
      expect(result?.name).toBe('Test Portfolio');
    });

    it('should return null for non-existent portfolio', async () => {
      const result = await portfolioService.getPortfolioById('non-existent-id');

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

  describe('updatePortfolio', () => {
    it('should update portfolio name', async () => {
      const created = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Original Name',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.updatePortfolio(created.id, {
        name: 'Updated Name',
      });

      expect(result.name).toBe('Updated Name');
    });

    it('should update risk profile', async () => {
      const created = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'conservative',
      });

      const result = await portfolioService.updatePortfolio(created.id, {
        riskProfile: 'aggressive',
      });

      expect(result.riskProfile).toBe('aggressive');
    });

    it('should update allocations', async () => {
      const created = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const newAllocations = [
        { asset: 'BTC', targetPercent: 70 },
        { asset: 'ETH', targetPercent: 30 },
      ];

      const result = await portfolioService.updatePortfolio(created.id, {
        allocations: newAllocations,
      });

      expect(result.allocations.length).toBe(2);
      expect(result.allocations[0].targetPercent).toBe(70);
    });
  });

  describe('deletePortfolio', () => {
    it('should delete a portfolio', async () => {
      const created = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'To Delete',
        riskProfile: 'moderate',
      });

      await portfolioService.deletePortfolio(created.id);

      const result = await portfolioService.getPortfolioById(created.id);
      expect(result).toBeNull();
    });

    it('should handle deletion of non-existent portfolio', async () => {
      await expect(
        portfolioService.deletePortfolio('non-existent-id')
      ).rejects.toThrow('Portfolio not found');
    });
  });

  describe('getPortfolioValue', () => {
    it('should calculate total portfolio value', async () => {
      mockGetPrices.mockResolvedValueOnce({
        BTC: 50000,
        ETH: 3000,
        USDT: 1,
      });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      // Add some holdings
      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.5,
        cost: 24000,
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'ETH',
        quantity: 2,
        cost: 5800,
      });

      const result = await portfolioService.getPortfolioValue(portfolio.id);

      expect(result.totalValue).toBeGreaterThan(0);
      expect(result.totalCost).toBe(29800);
      expect(result.unrealizedPnl).toBeDefined();
    });

    it('should handle portfolio with no holdings', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Empty Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.getPortfolioValue(portfolio.id);

      expect(result.totalValue).toBe(0);
      expect(result.totalCost).toBe(0);
    });

    it('should handle market data fetch errors gracefully', async () => {
      mockGetPrices.mockRejectedValueOnce(new Error('Market data unavailable'));

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await expect(
        portfolioService.getPortfolioValue(portfolio.id)
      ).rejects.toThrow('Market data unavailable');
    });
  });

  describe('getRebalanceRecommendations', () => {
    it('should recommend rebalancing when allocations deviate', async () => {
      mockGetPrices.mockResolvedValueOnce({
        BTC: 60000,
        ETH: 3500,
        USDT: 1,
      });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
        customAllocations: [
          { asset: 'BTC', targetPercent: 50 },
          { asset: 'ETH', targetPercent: 30 },
          { asset: 'USDT', targetPercent: 20 },
        ],
      });

      // Add holdings that deviate from target
      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 1,
        cost: 50000,
      });

      const result = await portfolioService.getRebalanceRecommendations(portfolio.id);

      expect(result).toBeDefined();
      expect(result.length).toBeGreaterThan(0);
      expect(result[0]).toHaveProperty('action');
      expect(result[0]).toHaveProperty('amount');
    });

    it('should not recommend rebalancing when allocations are balanced', async () => {
      mockGetPrices.mockResolvedValueOnce({
        BTC: 50000,
        ETH: 3000,
        USDT: 1,
      });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Balanced Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.getRebalanceRecommendations(portfolio.id);

      expect(result).toBeDefined();
      expect(result.filter(r => r.action !== 'hold')).toHaveLength(0);
    });

    it('should prioritize high-deviation assets', async () => {
      mockGetPrices.mockResolvedValueOnce({
        BTC: 70000,
        ETH: 3000,
        USDT: 1,
      });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.getRebalanceRecommendations(portfolio.id);

      const highPriority = result.filter(r => r.priority === 'high');
      expect(highPriority.length).toBeGreaterThanOrEqual(0);
    });
  });

  describe('createPortfolioGoal', () => {
    it('should create a new portfolio goal', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.createPortfolioGoal({
        userId: 'user-123',
        portfolioId: portfolio.id,
        name: 'Retirement Fund',
        targetAmount: 1000000,
        targetDate: new Date('2045-01-01'),
        monthlyContribution: 1000,
      });

      expect(result.name).toBe('Retirement Fund');
      expect(result.targetAmount).toBe(1000000);
      expect(result.monthlyContribution).toBe(1000);
    });

    it('should calculate goal progress', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.createPortfolioGoal({
        userId: 'user-123',
        portfolioId: portfolio.id,
        name: 'House Down Payment',
        targetAmount: 100000,
        targetDate: new Date('2026-01-01'),
        monthlyContribution: 2000,
        currentAmount: 25000,
      });

      expect(result.progress).toBe(25);
      expect(result.status).toBeDefined();
    });
  });

  describe('getPortfolioStats', () => {
    it('should calculate portfolio statistics', async () => {
      mockGetPrices.mockResolvedValue({
        BTC: 50000,
        ETH: 3000,
      });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.5,
        cost: 24000,
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'ETH',
        quantity: 2,
        cost: 5800,
      });

      const result = await portfolioService.getPortfolioStats(portfolio.id);

      expect(result.totalValue).toBeGreaterThan(0);
      expect(result).toHaveProperty('dayChange');
      expect(result).toHaveProperty('weekChange');
      expect(result).toHaveProperty('monthChange');
      expect(result).toHaveProperty('allTimeChange');
      expect(result).toHaveProperty('bestPerformer');
      expect(result).toHaveProperty('worstPerformer');
    });

    it('should handle portfolio with single asset', async () => {
      mockGetPrices.mockResolvedValue({ BTC: 50000 });

      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'BTC Only',
        riskProfile: 'aggressive',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 1,
        cost: 45000,
      });

      const result = await portfolioService.getPortfolioStats(portfolio.id);

      expect(result.totalValue).toBe(50000);
      expect(result.bestPerformer.asset).toBe('BTC');
    });
  });

  describe('addHolding', () => {
    it('should add a new holding to portfolio', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      const result = await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.5,
        cost: 25000,
      });

      expect(result.asset).toBe('BTC');
      expect(result.quantity).toBe(0.5);
      expect(result.cost).toBe(25000);
    });

    it('should update existing holding when adding to same asset', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.5,
        cost: 25000,
      });

      const result = await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.3,
        cost: 16000,
      });

      expect(result.quantity).toBe(0.8);
      expect(result.cost).toBe(41000);
    });
  });

  describe('removeHolding', () => {
    it('should remove a holding from portfolio', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'BTC',
        quantity: 0.5,
        cost: 25000,
      });

      await portfolioService.removeHolding(portfolio.id, 'BTC', 0.5);

      const updated = await portfolioService.getPortfolioById(portfolio.id);
      const btcHolding = updated?.allocations.find(a => a.asset === 'BTC');

      expect(btcHolding?.quantity).toBe(0);
    });

    it('should handle partial removal of holding', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'ETH',
        quantity: 5,
        cost: 15000,
      });

      await portfolioService.removeHolding(portfolio.id, 'ETH', 2);

      const updated = await portfolioService.getPortfolioById(portfolio.id);
      const ethHolding = updated?.allocations.find(a => a.asset === 'ETH');

      expect(ethHolding?.quantity).toBe(3);
    });

    it('should prevent removing more than available quantity', async () => {
      const portfolio = await portfolioService.createPortfolio({
        userId: 'user-123',
        name: 'Test Portfolio',
        riskProfile: 'moderate',
      });

      await portfolioService.addHolding(portfolio.id, {
        asset: 'SOL',
        quantity: 10,
        cost: 1000,
      });

      await expect(
        portfolioService.removeHolding(portfolio.id, 'SOL', 15)
      ).rejects.toThrow('Insufficient quantity');
    });
  });
});
