/**
 * Investment Account Service Unit Tests
 *
 * Tests for investment account service including:
 * - Account creation and management
 * - Balance tracking
 * - Performance calculations
 * - Account status management
 */

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

// Mock database (account service uses in-memory storage)
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 product service
const mockGetProductById = jest.fn();
jest.mock('../product.service', () => ({
  productService: {
    getProductById: mockGetProductById,
    getAllProducts: jest.fn(),
  },
}));

// Import service after mocks
import { accountService } from '../account.service';

describe('AccountService', () => {
  beforeEach(() => {
    resetDatabaseMocks();
    mockGetProductById.mockReset();
    jest.clearAllMocks();
  });

  describe('createAccount', () => {
    it('should create a new investment account', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas - El Guardián',
        riskProfile: 'conservative',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValueOnce(mockProduct);

      const result = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      expect(result.userId).toBe('user-123');
      expect(result.productId).toBe('product-123');
      expect(result.balance).toBe(1000);
      expect(result.initialInvestment).toBe(1000);
      expect(result.status).toBe('active');
    });

    it('should validate minimum investment amount', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'orion',
        name: 'Orion - El Explorador',
        riskProfile: 'moderate',
        minInvestment: 500,
        isActive: true,
      };

      mockGetProductById.mockResolvedValueOnce(mockProduct);

      await expect(
        accountService.createAccount({
          userId: 'user-123',
          productId: 'product-123',
          initialDeposit: 100,
        })
      ).rejects.toThrow('Minimum investment is 500');
    });

    it('should reject inactive products', async () => {
      const mockProduct = {
        id: 'product-124',
        code: 'inactive',
        name: 'Inactive Product',
        riskProfile: 'moderate',
        minInvestment: 100,
        isActive: false,
      };

      mockGetProductById.mockResolvedValueOnce(mockProduct);

      await expect(
        accountService.createAccount({
          userId: 'user-123',
          productId: 'product-124',
          initialDeposit: 1000,
        })
      ).rejects.toThrow('Product is not active');
    });

    it('should prevent duplicate accounts for same product', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        riskProfile: 'conservative',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await expect(
        accountService.createAccount({
          userId: 'user-123',
          productId: 'product-123',
          initialDeposit: 500,
        })
      ).rejects.toThrow('Account already exists for this product');
    });

    it('should handle non-existent product', async () => {
      mockGetProductById.mockResolvedValueOnce(null);

      await expect(
        accountService.createAccount({
          userId: 'user-123',
          productId: 'non-existent',
          initialDeposit: 1000,
        })
      ).rejects.toThrow('Product not found');
    });
  });

  describe('getUserAccounts', () => {
    it('should retrieve all accounts for a user', async () => {
      const mockProducts = [
        { id: 'product-1', code: 'atlas', name: 'Atlas' },
        { id: 'product-2', code: 'orion', name: 'Orion' },
      ];

      mockGetProductById.mockImplementation((id) =>
        Promise.resolve(mockProducts.find(p => p.id === id))
      );

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-1',
        initialDeposit: 1000,
      });

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-2',
        initialDeposit: 2000,
      });

      const result = await accountService.getUserAccounts('user-123');

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

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

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

    it('should include product information', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas - El Guardián',
        riskProfile: 'conservative',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.getUserAccounts('user-123');

      expect(result[0].product).toEqual(mockProduct);
    });
  });

  describe('getAccountById', () => {
    it('should retrieve an account by ID', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const created = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.getAccountById(created.id);

      expect(result).toBeDefined();
      expect(result?.id).toBe(created.id);
      expect(result?.product).toBeDefined();
    });

    it('should return null for non-existent account', async () => {
      const result = await accountService.getAccountById('non-existent');

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

  describe('getAccountByUserAndProduct', () => {
    it('should retrieve account by user and product', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.getAccountByUserAndProduct('user-123', 'product-123');

      expect(result).toBeDefined();
      expect(result?.userId).toBe('user-123');
      expect(result?.productId).toBe('product-123');
    });

    it('should return null if account does not exist', async () => {
      const result = await accountService.getAccountByUserAndProduct('user-999', 'product-999');

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

    it('should exclude closed accounts', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await accountService.closeAccount(account.id);

      const result = await accountService.getAccountByUserAndProduct('user-123', 'product-123');

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

  describe('updateAccountBalance', () => {
    it('should update account balance', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.updateAccountBalance(account.id, 1500);

      expect(result.balance).toBe(1500);
      expect(result.updatedAt).toBeDefined();
    });

    it('should calculate unrealized P&L', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.updateAccountBalance(account.id, 1200);

      expect(result.unrealizedPnl).toBe(200);
      expect(result.unrealizedPnlPercent).toBe(20);
    });

    it('should handle account not found', async () => {
      await expect(
        accountService.updateAccountBalance('non-existent', 1000)
      ).rejects.toThrow('Account not found');
    });
  });

  describe('closeAccount', () => {
    it('should close an active account', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.closeAccount(account.id);

      expect(result.status).toBe('closed');
      expect(result.closedAt).toBeDefined();
    });

    it('should require zero balance to close', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await expect(accountService.closeAccount(account.id)).rejects.toThrow(
        'Cannot close account with non-zero balance'
      );
    });

    it('should prevent closing already closed account', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await accountService.updateAccountBalance(account.id, 0);
      await accountService.closeAccount(account.id);

      await expect(accountService.closeAccount(account.id)).rejects.toThrow(
        'Account is already closed'
      );
    });
  });

  describe('suspendAccount', () => {
    it('should suspend an active account', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const result = await accountService.suspendAccount(account.id);

      expect(result.status).toBe('suspended');
    });

    it('should prevent operations on suspended account', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await accountService.suspendAccount(account.id);

      await expect(
        accountService.updateAccountBalance(account.id, 1500)
      ).rejects.toThrow('Account is suspended');
    });
  });

  describe('getAccountSummary', () => {
    it('should calculate account summary for user', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      const account2 = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-124',
        initialDeposit: 2000,
      });

      await accountService.updateAccountBalance(account2.id, 2500);

      const result = await accountService.getAccountSummary('user-123');

      expect(result.totalBalance).toBeGreaterThan(0);
      expect(result.totalDeposited).toBe(3000);
      expect(result.totalEarnings).toBeGreaterThan(0);
      expect(result.overallReturn).toBeGreaterThan(0);
      expect(result.accounts).toHaveLength(2);
    });

    it('should handle user with no accounts', async () => {
      const result = await accountService.getAccountSummary('user-999');

      expect(result.totalBalance).toBe(0);
      expect(result.totalDeposited).toBe(0);
      expect(result.accounts).toEqual([]);
    });

    it('should exclude closed accounts from summary', async () => {
      const mockProduct = {
        id: 'product-123',
        code: 'atlas',
        name: 'Atlas',
        minInvestment: 100,
        isActive: true,
      };

      mockGetProductById.mockResolvedValue(mockProduct);

      const account = await accountService.createAccount({
        userId: 'user-123',
        productId: 'product-123',
        initialDeposit: 1000,
      });

      await accountService.updateAccountBalance(account.id, 0);
      await accountService.closeAccount(account.id);

      const result = await accountService.getAccountSummary('user-123');

      expect(result.accounts).toHaveLength(0);
      expect(result.totalBalance).toBe(0);
    });
  });
});
