import { createMockRepository, createMockQueryBuilder, createMockCurrency } from '../helpers';
import { NotFoundError, ConflictError } from '../../shared/errors';

// Mock the entire module
const mockRepository = createMockRepository();
const mockQueryBuilder = createMockQueryBuilder();
mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);

jest.mock('../../config/typeorm', () => ({
  AppDataSource: {
    getRepository: jest.fn(() => mockRepository),
  },
}));

// Import after mocking
import { currenciesService } from '../../modules/core/currencies.service';

describe('CurrenciesService', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
  });

  describe('findAll', () => {
    it('should return all currencies ordered by code', async () => {
      const mockCurrencies = [
        createMockCurrency({ id: '1', code: 'EUR', name: 'Euro' }),
        createMockCurrency({ id: '2', code: 'MXN', name: 'Peso Mexicano' }),
        createMockCurrency({ id: '3', code: 'USD', name: 'Dólar Estadounidense' }),
      ];

      mockQueryBuilder.getMany.mockResolvedValue(mockCurrencies);

      const result = await currenciesService.findAll();

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('currency');
      expect(mockQueryBuilder.orderBy).toHaveBeenCalledWith('currency.code', 'ASC');
      expect(result).toEqual(mockCurrencies);
    });

    it('should filter active currencies when activeOnly is true', async () => {
      const activeCurrencies = [
        createMockCurrency({ id: '1', code: 'MXN', active: true }),
      ];

      mockQueryBuilder.getMany.mockResolvedValue(activeCurrencies);

      const result = await currenciesService.findAll(true);

      expect(mockQueryBuilder.where).toHaveBeenCalledWith('currency.active = :active', { active: true });
      expect(result).toEqual(activeCurrencies);
    });

    it('should return all currencies when activeOnly is false', async () => {
      const allCurrencies = [
        createMockCurrency({ id: '1', code: 'MXN', active: true }),
        createMockCurrency({ id: '2', code: 'USD', active: false }),
      ];

      mockQueryBuilder.getMany.mockResolvedValue(allCurrencies);

      const result = await currenciesService.findAll(false);

      expect(mockQueryBuilder.where).not.toHaveBeenCalled();
      expect(result).toHaveLength(2);
    });
  });

  describe('findById', () => {
    it('should return a currency by id', async () => {
      const mockCurrency = createMockCurrency({ id: 'currency-id-1' });
      mockRepository.findOne.mockResolvedValue(mockCurrency);

      const result = await currenciesService.findById('currency-id-1');

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { id: 'currency-id-1' },
      });
      expect(result).toEqual(mockCurrency);
    });

    it('should throw NotFoundError when currency not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      await expect(currenciesService.findById('non-existent')).rejects.toThrow(NotFoundError);
    });
  });

  describe('findByCode', () => {
    it('should return a currency by code', async () => {
      const mockCurrency = createMockCurrency({ code: 'MXN' });
      mockRepository.findOne.mockResolvedValue(mockCurrency);

      const result = await currenciesService.findByCode('MXN');

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { code: 'MXN' },
      });
      expect(result).toEqual(mockCurrency);
    });

    it('should convert lowercase code to uppercase', async () => {
      const mockCurrency = createMockCurrency({ code: 'USD' });
      mockRepository.findOne.mockResolvedValue(mockCurrency);

      await currenciesService.findByCode('usd');

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { code: 'USD' },
      });
    });

    it('should return null when currency not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      const result = await currenciesService.findByCode('XXX');

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

  describe('create', () => {
    it('should create a new currency', async () => {
      const newCurrency = createMockCurrency({ id: 'new-id', code: 'GBP', name: 'Libra Esterlina' });
      mockRepository.findOne.mockResolvedValue(null); // No existing currency
      mockRepository.create.mockReturnValue(newCurrency);
      mockRepository.save.mockResolvedValue(newCurrency);

      const result = await currenciesService.create({
        code: 'gbp',
        name: 'Libra Esterlina',
        symbol: '£',
      });

      expect(mockRepository.create).toHaveBeenCalledWith({
        code: 'GBP',
        name: 'Libra Esterlina',
        symbol: '£',
        decimals: 2,
      });
      expect(result).toEqual(newCurrency);
    });

    it('should throw ConflictError when currency code already exists', async () => {
      const existingCurrency = createMockCurrency({ code: 'MXN' });
      mockRepository.findOne.mockResolvedValue(existingCurrency);

      await expect(
        currenciesService.create({
          code: 'MXN',
          name: 'Peso Mexicano',
          symbol: '$',
        })
      ).rejects.toThrow(ConflictError);
    });

    it('should accept decimal_places parameter', async () => {
      const newCurrency = createMockCurrency({ decimals: 4 });
      mockRepository.findOne.mockResolvedValue(null);
      mockRepository.create.mockReturnValue(newCurrency);
      mockRepository.save.mockResolvedValue(newCurrency);

      await currenciesService.create({
        code: 'BTC',
        name: 'Bitcoin',
        symbol: '₿',
        decimal_places: 4,
      });

      expect(mockRepository.create).toHaveBeenCalledWith(
        expect.objectContaining({ decimals: 4 })
      );
    });
  });

  describe('update', () => {
    it('should update an existing currency', async () => {
      const existingCurrency = createMockCurrency({ id: 'currency-id' });
      const updatedCurrency = { ...existingCurrency, name: 'Updated Name' };

      mockRepository.findOne.mockResolvedValue(existingCurrency);
      mockRepository.save.mockResolvedValue(updatedCurrency);

      const result = await currenciesService.update('currency-id', { name: 'Updated Name' });

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

    it('should update active status', async () => {
      const existingCurrency = createMockCurrency({ id: 'currency-id', active: true });
      const updatedCurrency = { ...existingCurrency, active: false };

      mockRepository.findOne.mockResolvedValue(existingCurrency);
      mockRepository.save.mockResolvedValue(updatedCurrency);

      const result = await currenciesService.update('currency-id', { active: false });

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

    it('should throw NotFoundError when currency not found', async () => {
      mockRepository.findOne.mockResolvedValue(null);

      await expect(
        currenciesService.update('non-existent', { name: 'New Name' })
      ).rejects.toThrow(NotFoundError);
    });
  });
});
