import { FeatureRepository } from '../feature.repository';
import { ConflictError } from '../../../errors/http-error';

// EN: Mock Prisma client
// VI: Mock Prisma client
const mockPrismaClient = {
  feature: {
    findUnique: jest.fn(),
    findMany: jest.fn(),
    create: jest.fn(),
    update: jest.fn(),
    delete: jest.fn(),
    count: jest.fn(),
  },
  $transaction: jest.fn(),
};

jest.mock('../../../config/database.config', () => ({
  prisma: mockPrismaClient,
}));

describe('FeatureRepository', () => {
  let repository: FeatureRepository;
  let mockPrisma: any;

  beforeEach(() => {
    jest.clearAllMocks();
    repository = new FeatureRepository();
    mockPrisma = mockPrismaClient;
  });

  describe('findById', () => {
    it('should return feature when found', async () => {
      const mockFeature = { id: '1', name: 'test-feature', enabled: true };
      mockPrisma.feature.findUnique.mockResolvedValue(mockFeature);

      const result = await repository.findById('1');

      expect(mockPrisma.feature.findUnique).toHaveBeenCalledWith({ where: { id: '1' } });
      expect(result).toEqual(mockFeature);
    });

    it('should return null when feature not found', async () => {
      mockPrisma.feature.findUnique.mockResolvedValue(null);

      const result = await repository.findById('1');

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

  describe('findByName', () => {
    it('should return feature when found by name', async () => {
      const mockFeature = { id: '1', name: 'test-feature', enabled: true };
      mockPrisma.feature.findUnique.mockResolvedValue(mockFeature);

      const result = await repository.findByName('test-feature');

      expect(mockPrisma.feature.findUnique).toHaveBeenCalledWith({
        where: { name: 'test-feature' }
      });
      expect(result).toEqual(mockFeature);
    });
  });

  describe('findAll', () => {
    it('should return all features with default options', async () => {
      const mockFeatures = [
        { id: '1', name: 'feature-1' },
        { id: '2', name: 'feature-2' },
      ];
      mockPrisma.feature.findMany.mockResolvedValue(mockFeatures);

      const result = await repository.findAll();

      expect(mockPrisma.feature.findMany).toHaveBeenCalledWith({});
      expect(result).toEqual(mockFeatures);
    });

    it('should return features with custom options', async () => {
      const options = { where: { enabled: true }, orderBy: { createdAt: 'desc' } };
      const mockFeatures = [{ id: '1', name: 'enabled-feature' }];
      mockPrisma.feature.findMany.mockResolvedValue(mockFeatures);

      const result = await repository.findAll(options);

      expect(mockPrisma.feature.findMany).toHaveBeenCalledWith(options);
      expect(result).toEqual(mockFeatures);
    });
  });

  describe('create', () => {
    it('should create feature successfully when name is unique', async () => {
      const createData = { name: 'new-feature', title: 'New Feature' };
      const mockFeature = { id: '1', ...createData, enabled: true };

      // Mock no existing feature
      mockPrisma.feature.findUnique.mockResolvedValue(null);
      mockPrisma.feature.create.mockResolvedValue(mockFeature);

      const result = await repository.create(createData);

      expect(mockPrisma.feature.findUnique).toHaveBeenCalledWith({
        where: { name: 'new-feature' }
      });
      expect(mockPrisma.feature.create).toHaveBeenCalledWith({ data: createData });
      expect(result).toEqual(mockFeature);
    });

    it('should throw ConflictError when feature name already exists', async () => {
      const createData = { name: 'existing-feature' };
      const existingFeature = { id: '1', name: 'existing-feature' };

      mockPrisma.feature.findUnique.mockResolvedValue(existingFeature);

      await expect(repository.create(createData)).rejects.toThrow(ConflictError);
      expect(mockPrisma.feature.create).not.toHaveBeenCalled();
    });
  });

  describe('update', () => {
    it('should update feature successfully', async () => {
      const updateData = { title: 'Updated Title' };
      const mockFeature = { id: '1', name: 'test-feature', title: 'Updated Title' };

      mockPrisma.feature.update.mockResolvedValue(mockFeature);

      const result = await repository.update('1', updateData);

      expect(mockPrisma.feature.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: updateData,
      });
      expect(result).toEqual(mockFeature);
    });
  });

  describe('delete', () => {
    it('should delete feature successfully', async () => {
      mockPrisma.feature.delete.mockResolvedValue({});

      const result = await repository.delete('1');

      expect(mockPrisma.feature.delete).toHaveBeenCalledWith({
        where: { id: '1' }
      });
      expect(result).toBe(true);
    });
  });

  describe('count', () => {
    it('should return count of features', async () => {
      mockPrisma.feature.count.mockResolvedValue(5);

      const result = await repository.count();

      expect(mockPrisma.feature.count).toHaveBeenCalledWith({ where: undefined });
      expect(result).toBe(5);
    });

    it('should return count with where clause', async () => {
      const where = { enabled: true };
      mockPrisma.feature.count.mockResolvedValue(3);

      const result = await repository.count(where);

      expect(mockPrisma.feature.count).toHaveBeenCalledWith({ where });
      expect(result).toBe(3);
    });
  });

  describe('exists', () => {
    it('should return true when feature exists', async () => {
      mockPrisma.feature.count.mockResolvedValue(1);

      const result = await repository.exists('1');

      expect(mockPrisma.feature.count).toHaveBeenCalledWith({ where: { id: '1' } });
      expect(result).toBe(true);
    });

    it('should return false when feature does not exist', async () => {
      mockPrisma.feature.count.mockResolvedValue(0);

      const result = await repository.exists('1');

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

  describe('toggleEnabled', () => {
    it('should toggle feature from disabled to enabled', async () => {
      const existingFeature = { id: '1', name: 'test-feature', enabled: false };
      const updatedFeature = { ...existingFeature, enabled: true };

      mockPrisma.feature.findUnique.mockResolvedValue(existingFeature);
      mockPrisma.feature.update.mockResolvedValue(updatedFeature);

      const result = await repository.toggleEnabled('1');

      expect(mockPrisma.feature.update).toHaveBeenCalledWith({
        where: { id: '1' },
        data: { enabled: true },
      });
      expect(result).toEqual(updatedFeature);
    });

    it('should throw ConflictError when feature not found', async () => {
      mockPrisma.feature.findUnique.mockResolvedValue(null);

      await expect(repository.toggleEnabled('1')).rejects.toThrow(ConflictError);
      expect(mockPrisma.feature.update).not.toHaveBeenCalled();
    });
  });

  describe('findByTags', () => {
    it('should return features matching tags', async () => {
      const tags = ['web', 'api'];
      const mockFeatures = [
        { id: '1', name: 'web-feature', tags: ['web'] },
        { id: '2', name: 'api-feature', tags: ['api'] },
      ];

      mockPrisma.feature.findMany.mockResolvedValue(mockFeatures);

      const result = await repository.findByTags(tags);

      expect(mockPrisma.feature.findMany).toHaveBeenCalledWith({
        where: {
          tags: {
            hasSome: tags,
          },
        },
        orderBy: { createdAt: 'desc' },
      });
      expect(result).toEqual(mockFeatures);
    });
  });

  describe('findEnabled', () => {
    it('should return only enabled features', async () => {
      const mockFeatures = [
        { id: '1', name: 'enabled-feature', enabled: true },
        { id: '2', name: 'disabled-feature', enabled: false },
      ];

      mockPrisma.feature.findMany.mockResolvedValue([mockFeatures[0]]);

      const result = await repository.findEnabled();

      expect(mockPrisma.feature.findMany).toHaveBeenCalledWith({
        where: { enabled: true },
        orderBy: { createdAt: 'desc' },
      });
      expect(result).toEqual([mockFeatures[0]]);
    });
  });

  describe('search', () => {
    it('should search features by query', async () => {
      const query = 'test';
      const mockFeatures = [
        { id: '1', name: 'test-feature', title: 'Test Feature' },
      ];

      mockPrisma.feature.findMany.mockResolvedValue(mockFeatures);

      const result = await repository.search(query);

      expect(mockPrisma.feature.findMany).toHaveBeenCalledWith({
        where: {
          OR: [
            { name: { contains: query, mode: 'insensitive' } },
            { title: { contains: query, mode: 'insensitive' } },
            { description: { contains: query, mode: 'insensitive' } },
          ],
        },
        take: 10,
        orderBy: { createdAt: 'desc' },
      });
      expect(result).toEqual(mockFeatures);
    });
  });

  describe('getStatistics', () => {
    it('should return feature statistics', async () => {
      const mockFeatures = [
        { id: '1', name: 'feature1', tags: ['web', 'api'], enabled: true },
        { id: '2', name: 'feature2', tags: ['web'], enabled: false },
        { id: '3', name: 'feature3', tags: ['mobile'], enabled: true },
      ];

      mockPrisma.feature.count
        .mockResolvedValueOnce(3) // total
        .mockResolvedValueOnce(2) // enabled
        .mockResolvedValueOnce(1); // disabled

      mockPrisma.feature.findMany.mockResolvedValue(mockFeatures);

      const result = await repository.getStatistics();

      expect(result).toEqual({
        total: 3,
        enabled: 2,
        disabled: 1,
        byTag: {
          web: 2,
          api: 1,
          mobile: 1,
        },
      });
    });
  });
});