import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { NotFoundException } from '@nestjs/common';
import { ILike } from 'typeorm';
import { WordsService } from './words.service';
import { Word } from './entities/word.entity';

// ---------------------------------------------------------------------------
// Repository mock factory
// ---------------------------------------------------------------------------
const mockRepo = () => ({
  find: jest.fn(),
  findOne: jest.fn(),
  create: jest.fn(),
  save: jest.fn(),
  remove: jest.fn(),
});

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('WordsService', () => {
  let service: WordsService;
  let repo: ReturnType<typeof mockRepo>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        WordsService,
        { provide: getRepositoryToken(Word), useFactory: mockRepo },
      ],
    }).compile();

    service = module.get<WordsService>(WordsService);
    repo = module.get(getRepositoryToken(Word));
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findAll', () => {
    it('returns all words when no search query', async () => {
      const words: Partial<Word>[] = [{ id: '1', text: 'hello' }];
      repo.find.mockResolvedValue(words);

      const result = await service.findAll();

      expect(repo.find).toHaveBeenCalledWith({ order: { createdAt: 'DESC' } });
      expect(result).toEqual(words);
    });

    it('filters by search query using ILike', async () => {
      const words: Partial<Word>[] = [{ id: '1', text: 'hello' }];
      repo.find.mockResolvedValue(words);

      await service.findAll('hell');

      expect(repo.find).toHaveBeenCalledWith({
        where: { text: ILike('%hell%') },
        order: { createdAt: 'DESC' },
      });
    });
  });

  describe('findOne', () => {
    it('throws NotFoundException when word does not exist', async () => {
      repo.findOne.mockResolvedValue(null);
      await expect(service.findOne('missing-id')).rejects.toThrow(
        NotFoundException,
      );
    });

    it('returns the word when found', async () => {
      const word: Partial<Word> = { id: '1', text: 'hello' };
      repo.findOne.mockResolvedValue(word);
      const result = await service.findOne('1');
      expect(result).toEqual(word);
    });
  });

  describe('create', () => {
    it('persists and returns the new word', async () => {
      const word: Partial<Word> = { id: '1', text: 'hello' };
      repo.create.mockReturnValue(word);
      repo.save.mockResolvedValue(word);

      const result = await service.create('hello');

      expect(repo.create).toHaveBeenCalledWith({ text: 'hello' });
      expect(result).toEqual(word);
    });
  });
});
