import { createMockRepository, createMockQueryBuilder, createMockState, createMockCountry } from '../helpers';
import { NotFoundError } from '../../shared/errors';

const mockRepository = createMockRepository();
const mockQueryBuilder = createMockQueryBuilder();
mockRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);

const mockCountryRepository = createMockRepository();

jest.mock('../../config/typeorm', () => ({
  AppDataSource: {
    getRepository: jest.fn((entity: any) => {
      if (entity.name === 'Country') return mockCountryRepository;
      return mockRepository;
    }),
  },
}));

import { statesService } from '../../modules/core/states.service';

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

  describe('findAll', () => {
    it('should return all states with country relation', async () => {
      const mockStates = [
        createMockState({ id: '1', code: 'JAL', name: 'Jalisco' }),
        createMockState({ id: '2', code: 'NLE', name: 'Nuevo León' }),
      ];

      mockQueryBuilder.getMany.mockResolvedValue(mockStates);

      const result = await statesService.findAll();

      expect(mockRepository.createQueryBuilder).toHaveBeenCalledWith('state');
      expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith('state.country', 'country');
      expect(result).toEqual(mockStates);
    });

    it('should accept filter options', async () => {
      mockQueryBuilder.getMany.mockResolvedValue([]);

      const result = await statesService.findAll({ active: true });

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

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

      const result = await statesService.findById('state-id-1');

      expect(mockRepository.findOne).toHaveBeenCalledWith({
        where: { id: 'state-id-1' },
        relations: ['country'],
      });
      expect(result).toEqual(mockState);
    });

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

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

  describe('findByCountry', () => {
    it('should return states by country id', async () => {
      const mockStates = [
        createMockState({ countryId: 'country-1', code: 'JAL' }),
      ];

      mockRepository.find.mockResolvedValue(mockStates);

      const result = await statesService.findByCountry('country-1');

      expect(result).toEqual(mockStates);
    });
  });

  describe('findByCountryCode', () => {
    it('should return states filtered by country code', async () => {
      const mockStates = [createMockState({ countryId: 'country-mx' })];
      mockQueryBuilder.getMany.mockResolvedValue(mockStates);

      const result = await statesService.findByCountryCode('MX');

      expect(result).toEqual(mockStates);
    });
  });

  describe('create', () => {
    it('should create a new state', async () => {
      const mockCountry = createMockCountry({ id: 'country-mx' });
      const newState = createMockState({ id: 'new-state', code: 'AGS', name: 'Aguascalientes' });

      mockCountryRepository.findOne.mockResolvedValue(mockCountry);
      mockRepository.findOne.mockResolvedValue(null);
      mockRepository.create.mockReturnValue(newState);
      mockRepository.save.mockResolvedValue(newState);

      const result = await statesService.create({
        countryId: 'country-mx',
        code: 'AGS',
        name: 'Aguascalientes',
      });

      expect(result).toEqual(newState);
    });

    // Note: validation of country existence is tested at the integration level
  });

  describe('update', () => {
    it('should update an existing state', async () => {
      const existingState = createMockState({ id: 'state-id' });
      const updatedState = { ...existingState, name: 'Updated State' };

      mockRepository.findOne.mockResolvedValue(existingState);
      mockRepository.save.mockResolvedValue(updatedState);

      const result = await statesService.update('state-id', { name: 'Updated State' });

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

  describe('delete', () => {
    it('should delete a state', async () => {
      const existingState = createMockState({ id: 'state-to-delete' });
      mockRepository.findOne.mockResolvedValue(existingState);
      mockRepository.remove.mockResolvedValue(existingState);

      await statesService.delete('state-to-delete');

      expect(mockRepository.remove).toHaveBeenCalledWith(existingState);
    });

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

      await expect(statesService.delete('non-existent')).rejects.toThrow(NotFoundError);
    });
  });
});
