import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { NotFoundException, BadRequestException } from '@nestjs/common';
import { PasswordResetService } from './password-reset.service';
import { PasswordResetToken } from './entities/password-reset-token.entity';
import { User } from '../../database/entities/user.entity';
import { EmailService } from '../../common/services/email.service';
import * as bcrypt from 'bcrypt';

describe('PasswordResetService', () => {
  let service: PasswordResetService;
  let passwordResetTokenRepository: Repository<PasswordResetToken>;
  let userRepository: Repository<User>;
  let emailService: EmailService;

  const mockUser = {
    id: 'user_123',
    email: 'test@example.com',
    name: 'Test User',
    passwordHash: 'old_hash',
  };

  const mockToken = {
    id: 'reset_123',
    userId: 'user_123',
    token: 'reset_token_abc',
    expiresAt: new Date(Date.now() + 60 * 60 * 1000),
    used: false,
    createdAt: new Date(),
  };

  const mockPasswordResetTokenRepository = {
    findOne: jest.fn(),
    create: jest.fn(),
    save: jest.fn(),
    update: jest.fn(),
    delete: jest.fn(),
  };

  const mockUserRepository = {
    findOne: jest.fn(),
    update: jest.fn(),
  };

  const mockEmailService = {
    sendEmail: jest.fn().mockResolvedValue(true),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PasswordResetService,
        { provide: getRepositoryToken(PasswordResetToken), useValue: mockPasswordResetTokenRepository },
        { provide: getRepositoryToken(User), useValue: mockUserRepository },
        { provide: EmailService, useValue: mockEmailService },
      ],
    }).compile();

    service = module.get<PasswordResetService>(PasswordResetService);
    passwordResetTokenRepository = module.get<Repository<PasswordResetToken>>(getRepositoryToken(PasswordResetToken));
    userRepository = module.get<Repository<User>>(getRepositoryToken(User));
    emailService = module.get<EmailService>(EmailService);
    jest.clearAllMocks();
  });

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

  describe('requestPasswordReset', () => {
    it('should create reset token and send email', async () => {
      mockUserRepository.findOne.mockResolvedValue(mockUser);
      mockPasswordResetTokenRepository.create.mockReturnValue(mockToken);
      mockPasswordResetTokenRepository.save.mockResolvedValue(mockToken);

      const result = await service.requestPasswordReset('test@example.com');

      expect(result.success).toBe(true);
      expect(passwordResetTokenRepository.save).toHaveBeenCalled();
      expect(emailService.sendEmail).toHaveBeenCalledWith(expect.objectContaining({
        to: mockUser.email,
        subject: expect.stringContaining('Password Reset'),
      }));
    });

    it('should not reveal if email does not exist', async () => {
      mockUserRepository.findOne.mockResolvedValue(null);
      const result = await service.requestPasswordReset('nonexistent@example.com');
      expect(result.success).toBe(true);
      expect(emailService.sendEmail).not.toHaveBeenCalled();
    });
  });

  describe('verifyResetToken', () => {
    it('should verify valid token', async () => {
      mockPasswordResetTokenRepository.findOne.mockResolvedValue(mockToken);
      const result = await service.verifyResetToken('reset_token_abc');
      expect(result.valid).toBe(true);
    });

    it('should reject expired token', async () => {
      const expiredToken = { ...mockToken, expiresAt: new Date(Date.now() - 1000) };
      mockPasswordResetTokenRepository.findOne.mockResolvedValue(expiredToken);
      const result = await service.verifyResetToken('reset_token_abc');
      expect(result.valid).toBe(false);
    });

    it('should reject used token', async () => {
      mockPasswordResetTokenRepository.findOne.mockResolvedValue({ ...mockToken, used: true });
      const result = await service.verifyResetToken('reset_token_abc');
      expect(result.valid).toBe(false);
    });
  });

  describe('resetPassword', () => {
    it('should reset password with valid token', async () => {
      mockPasswordResetTokenRepository.findOne.mockResolvedValue(mockToken);
      mockUserRepository.update.mockResolvedValue({ affected: 1 });
      mockPasswordResetTokenRepository.update.mockResolvedValue({ affected: 1 });

      const result = await service.resetPassword('reset_token_abc', 'NewPassword123!');

      expect(result.success).toBe(true);
      expect(userRepository.update).toHaveBeenCalledWith('user_123', expect.objectContaining({
        passwordHash: expect.any(String),
      }));
      expect(passwordResetTokenRepository.update).toHaveBeenCalledWith('reset_123', { used: true });
    });

    it('should throw BadRequestException for invalid token', async () => {
      mockPasswordResetTokenRepository.findOne.mockResolvedValue(null);
      await expect(service.resetPassword('invalid_token', 'NewPassword123!')).rejects.toThrow(BadRequestException);
    });

    it('should throw BadRequestException for expired token', async () => {
      const expiredToken = { ...mockToken, expiresAt: new Date(Date.now() - 1000) };
      mockPasswordResetTokenRepository.findOne.mockResolvedValue(expiredToken);
      await expect(service.resetPassword('reset_token_abc', 'NewPassword123!')).rejects.toThrow(BadRequestException);
    });
  });
});
