/**
 * Investment Product Service Unit Tests
 *
 * Tests for investment product service including:
 * - Product retrieval and filtering
 * - Product validation
 * - Risk profile matching
 */

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

// 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(),
  },
}));

// Import service after mocks
import { productService } from '../product.service';

describe('ProductService', () => {
  beforeEach(() => {
    resetDatabaseMocks();
    jest.clearAllMocks();
  });

  describe('getAllProducts', () => {
    it('should retrieve all investment products', async () => {
      const result = await productService.getAllProducts();

      expect(result).toBeDefined();
      expect(result.length).toBeGreaterThan(0);
      expect(result[0]).toHaveProperty('id');
      expect(result[0]).toHaveProperty('code');
      expect(result[0]).toHaveProperty('name');
      expect(result[0]).toHaveProperty('riskProfile');
    });

    it('should return products with all required fields', async () => {
      const result = await productService.getAllProducts();

      result.forEach(product => {
        expect(product).toHaveProperty('id');
        expect(product).toHaveProperty('code');
        expect(product).toHaveProperty('name');
        expect(product).toHaveProperty('description');
        expect(product).toHaveProperty('riskProfile');
        expect(product).toHaveProperty('targetReturnMin');
        expect(product).toHaveProperty('targetReturnMax');
        expect(product).toHaveProperty('minInvestment');
        expect(product).toHaveProperty('managementFee');
        expect(product).toHaveProperty('performanceFee');
        expect(product).toHaveProperty('isActive');
      });
    });

    it('should filter active products only', async () => {
      const result = await productService.getAllProducts({ activeOnly: true });

      expect(result.every(p => p.isActive)).toBe(true);
    });
  });

  describe('getProductById', () => {
    it('should retrieve a product by ID', async () => {
      const allProducts = await productService.getAllProducts();
      const productId = allProducts[0].id;

      const result = await productService.getProductById(productId);

      expect(result).toBeDefined();
      expect(result?.id).toBe(productId);
    });

    it('should return null for non-existent product', async () => {
      const result = await productService.getProductById('non-existent-id');

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

  describe('getProductByCode', () => {
    it('should retrieve Atlas product by code', async () => {
      const result = await productService.getProductByCode('atlas');

      expect(result).toBeDefined();
      expect(result?.code).toBe('atlas');
      expect(result?.name).toContain('Atlas');
      expect(result?.riskProfile).toBe('conservative');
    });

    it('should retrieve Orion product by code', async () => {
      const result = await productService.getProductByCode('orion');

      expect(result).toBeDefined();
      expect(result?.code).toBe('orion');
      expect(result?.name).toContain('Orion');
      expect(result?.riskProfile).toBe('moderate');
    });

    it('should retrieve Nova product by code', async () => {
      const result = await productService.getProductByCode('nova');

      expect(result).toBeDefined();
      expect(result?.code).toBe('nova');
      expect(result?.name).toContain('Nova');
      expect(result?.riskProfile).toBe('aggressive');
    });

    it('should return null for invalid product code', async () => {
      const result = await productService.getProductByCode('invalid-code');

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

    it('should be case-insensitive', async () => {
      const result1 = await productService.getProductByCode('ATLAS');
      const result2 = await productService.getProductByCode('atlas');
      const result3 = await productService.getProductByCode('Atlas');

      expect(result1?.code).toBe('atlas');
      expect(result2?.code).toBe('atlas');
      expect(result3?.code).toBe('atlas');
    });
  });

  describe('getProductsByRiskProfile', () => {
    it('should retrieve conservative products', async () => {
      const result = await productService.getProductsByRiskProfile('conservative');

      expect(result).toBeDefined();
      expect(result.length).toBeGreaterThan(0);
      expect(result.every(p => p.riskProfile === 'conservative')).toBe(true);
    });

    it('should retrieve moderate products', async () => {
      const result = await productService.getProductsByRiskProfile('moderate');

      expect(result).toBeDefined();
      expect(result.length).toBeGreaterThan(0);
      expect(result.every(p => p.riskProfile === 'moderate')).toBe(true);
    });

    it('should retrieve aggressive products', async () => {
      const result = await productService.getProductsByRiskProfile('aggressive');

      expect(result).toBeDefined();
      expect(result.length).toBeGreaterThan(0);
      expect(result.every(p => p.riskProfile === 'aggressive')).toBe(true);
    });

    it('should return empty array for invalid risk profile', async () => {
      const result = await productService.getProductsByRiskProfile('invalid' as any);

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

  describe('product characteristics', () => {
    it('should have Atlas with lowest minimum investment', async () => {
      const atlas = await productService.getProductByCode('atlas');

      expect(atlas).toBeDefined();
      expect(atlas?.minInvestment).toBeLessThanOrEqual(100);
    });

    it('should have conservative products with lower drawdown', async () => {
      const conservativeProducts = await productService.getProductsByRiskProfile('conservative');

      conservativeProducts.forEach(product => {
        expect(product.maxDrawdown).toBeLessThanOrEqual(10);
      });
    });

    it('should have aggressive products with higher return targets', async () => {
      const aggressiveProducts = await productService.getProductsByRiskProfile('aggressive');

      aggressiveProducts.forEach(product => {
        expect(product.targetReturnMax).toBeGreaterThan(10);
      });
    });

    it('should have performance fee defined for all products', async () => {
      const allProducts = await productService.getAllProducts();

      allProducts.forEach(product => {
        expect(product.performanceFee).toBeGreaterThanOrEqual(0);
        expect(product.performanceFee).toBeLessThanOrEqual(100);
      });
    });

    it('should have valid target return ranges', async () => {
      const allProducts = await productService.getAllProducts();

      allProducts.forEach(product => {
        expect(product.targetReturnMin).toBeGreaterThan(0);
        expect(product.targetReturnMax).toBeGreaterThan(product.targetReturnMin);
      });
    });
  });

  describe('product features', () => {
    it('should have Atlas with conservative features', async () => {
      const atlas = await productService.getProductByCode('atlas');

      expect(atlas?.features).toBeDefined();
      expect(atlas?.features.length).toBeGreaterThan(0);
      expect(atlas?.strategy).toBeDefined();
      expect(atlas?.assets).toBeDefined();
      expect(atlas?.assets).toContain('BTC');
      expect(atlas?.assets).toContain('ETH');
    });

    it('should have Orion with moderate features', async () => {
      const orion = await productService.getProductByCode('orion');

      expect(orion?.features).toBeDefined();
      expect(orion?.strategy).toBeDefined();
      expect(orion?.assets).toBeDefined();
      expect(orion?.assets.length).toBeGreaterThan(2);
    });

    it('should have Nova with aggressive features', async () => {
      const nova = await productService.getProductByCode('nova');

      expect(nova?.features).toBeDefined();
      expect(nova?.strategy).toBeDefined();
      expect(nova?.assets).toBeDefined();
      expect(nova?.tradingFrequency).toBeDefined();
    });

    it('should have all products with descriptions', async () => {
      const allProducts = await productService.getAllProducts();

      allProducts.forEach(product => {
        expect(product.description).toBeDefined();
        expect(product.description.length).toBeGreaterThan(50);
      });
    });
  });

  describe('getRecommendedProduct', () => {
    it('should recommend conservative product for low risk tolerance', async () => {
      const result = await productService.getRecommendedProduct({
        riskTolerance: 'low',
        investmentAmount: 1000,
      });

      expect(result).toBeDefined();
      expect(result?.riskProfile).toBe('conservative');
      expect(result?.minInvestment).toBeLessThanOrEqual(1000);
    });

    it('should recommend moderate product for medium risk tolerance', async () => {
      const result = await productService.getRecommendedProduct({
        riskTolerance: 'medium',
        investmentAmount: 5000,
      });

      expect(result).toBeDefined();
      expect(result?.riskProfile).toBe('moderate');
    });

    it('should recommend aggressive product for high risk tolerance', async () => {
      const result = await productService.getRecommendedProduct({
        riskTolerance: 'high',
        investmentAmount: 10000,
      });

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

    it('should filter by minimum investment amount', async () => {
      const result = await productService.getRecommendedProduct({
        riskTolerance: 'high',
        investmentAmount: 200,
      });

      expect(result).toBeDefined();
      if (result) {
        expect(result.minInvestment).toBeLessThanOrEqual(200);
      }
    });

    it('should return null if no product matches criteria', async () => {
      const result = await productService.getRecommendedProduct({
        riskTolerance: 'low',
        investmentAmount: 10,
      });

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

  describe('validateProduct', () => {
    it('should validate active product', async () => {
      const allProducts = await productService.getAllProducts({ activeOnly: true });
      const product = allProducts[0];

      const result = await productService.validateProduct(product.id);

      expect(result).toBe(true);
    });

    it('should reject inactive product', async () => {
      const result = await productService.validateProduct('inactive-product-id');

      expect(result).toBe(false);
    });

    it('should reject non-existent product', async () => {
      const result = await productService.validateProduct('non-existent-id');

      expect(result).toBe(false);
    });
  });

  describe('getProductPerformanceMetrics', () => {
    it('should retrieve performance metrics for a product', async () => {
      const atlas = await productService.getProductByCode('atlas');

      const result = await productService.getProductPerformanceMetrics(atlas!.id);

      expect(result).toBeDefined();
      expect(result).toHaveProperty('targetReturnMin');
      expect(result).toHaveProperty('targetReturnMax');
      expect(result).toHaveProperty('maxDrawdown');
      expect(result).toHaveProperty('sharpeRatio');
      expect(result).toHaveProperty('volatility');
    });

    it('should calculate risk-adjusted returns', async () => {
      const orion = await productService.getProductByCode('orion');

      const result = await productService.getProductPerformanceMetrics(orion!.id);

      expect(result.sharpeRatio).toBeGreaterThan(0);
      expect(result.volatility).toBeGreaterThan(0);
    });
  });

  describe('compareProducts', () => {
    it('should compare two products', async () => {
      const atlas = await productService.getProductByCode('atlas');
      const nova = await productService.getProductByCode('nova');

      const result = await productService.compareProducts(atlas!.id, nova!.id);

      expect(result).toBeDefined();
      expect(result.product1).toEqual(atlas);
      expect(result.product2).toEqual(nova);
      expect(result.comparison).toBeDefined();
      expect(result.comparison).toHaveProperty('riskDifference');
      expect(result.comparison).toHaveProperty('returnDifference');
      expect(result.comparison).toHaveProperty('feeDifference');
    });

    it('should highlight key differences', async () => {
      const atlas = await productService.getProductByCode('atlas');
      const orion = await productService.getProductByCode('orion');

      const result = await productService.compareProducts(atlas!.id, orion!.id);

      expect(result.comparison.riskDifference).not.toBe(0);
      expect(result.comparison.returnDifference).toBeGreaterThan(0);
    });
  });
});
