const RegisterUserCommandHandler = require('../../../src/application/auth/commands/RegisterUserCommandHandler');
const RegisterUserCommand = require('../../../src/application/auth/commands/RegisterUserCommand');
const bcrypt = require('bcryptjs');
const JwtService = require('../../../src/application/services/JwtService');

// Mock dependencies
jest.mock('bcryptjs');
jest.mock('../../../src/application/services/JwtService');

describe('RegisterUserCommandHandler', () => {
  let handler;
  let mockPrisma;
  let mockEmailService;
  let mockJwtService;

  beforeEach(() => {
    // Mock JwtService instance
    mockJwtService = {
      generateToken: jest.fn()
    };
    JwtService.mockImplementation(() => mockJwtService);

    // Mock Prisma
    mockPrisma = {
      user: {
        findUnique: jest.fn(),
        create: jest.fn()
      }
    };

    // Mock EmailService
    mockEmailService = {
      sendWelcomeEmail: jest.fn().mockResolvedValue(true)
    };

    handler = new RegisterUserCommandHandler(mockPrisma, mockEmailService);

    // Reset all mocks
    jest.clearAllMocks();
  });

  describe('handle - success cases', () => {
    it('should register a new user successfully', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
      
      mockPrisma.user.findUnique.mockResolvedValue(null); // No existing user
      bcrypt.hash.mockResolvedValue('hashed_password');
      mockPrisma.user.create.mockResolvedValue({
        id: 1,
        name: 'John Doe',
        email: 'john@example.com',
        password: 'hashed_password',
        createdAt: new Date(),
        updatedAt: new Date()
      });
      mockJwtService.generateToken.mockReturnValue('mock_jwt_token');

      // Act
      const result = await handler.handle(command);

      // Assert
      expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
        where: { email: 'john@example.com' }
      });
      expect(bcrypt.hash).toHaveBeenCalledWith('password123', 10);
      expect(mockPrisma.user.create).toHaveBeenCalledWith({
        data: {
          name: 'John Doe',
          email: 'john@example.com',
          password: 'hashed_password'
        }
      });
      expect(mockJwtService.generateToken).toHaveBeenCalledWith({
        userId: 1,
        email: 'john@example.com'
      });
      expect(result.user).toEqual({
        id: 1,
        name: 'John Doe',
        email: 'john@example.com',
        createdAt: expect.any(Date),
        updatedAt: expect.any(Date)
      });
      expect(result.token).toBe('mock_jwt_token');
      expect(result.user.password).toBeUndefined(); // Password should not be returned
    });

    it('should send welcome email after registration', async () => {
      // Arrange
      const command = new RegisterUserCommand('Jane Doe', 'jane@example.com', 'password123');
      
      mockPrisma.user.findUnique.mockResolvedValue(null);
      bcrypt.hash.mockResolvedValue('hashed_password');
      mockPrisma.user.create.mockResolvedValue({
        id: 2,
        name: 'Jane Doe',
        email: 'jane@example.com',
        password: 'hashed_password',
        createdAt: new Date(),
        updatedAt: new Date()
      });
      mockJwtService.generateToken.mockReturnValue('mock_jwt_token');

      // Act
      await handler.handle(command);

      // Assert
      expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledWith('jane@example.com', 'Jane Doe');
    });
  });

  describe('handle - validation errors', () => {
    it('should throw error if name is missing', async () => {
      // Arrange
      const command = new RegisterUserCommand('', 'john@example.com', 'password123');

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
    });

    it('should throw error if email is missing', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', '', 'password123');

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
    });

    it('should throw error if password is missing', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'john@example.com', '');

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('Name, email and password are required');
    });

    it('should throw error if password is too short', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'john@example.com', '12345');

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('Password must be at least 6 characters long');
    });

    it('should throw error if email format is invalid', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'invalid-email', 'password123');

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('Invalid email format');
    });

    it('should throw error if user already exists', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
      
      mockPrisma.user.findUnique.mockResolvedValue({
        id: 1,
        email: 'john@example.com'
      });

      // Act & Assert
      await expect(handler.handle(command)).rejects.toThrow('User with this email already exists');
    });
  });

  describe('handle - error handling', () => {
    it('should not fail if email service throws error', async () => {
      // Arrange
      const command = new RegisterUserCommand('John Doe', 'john@example.com', 'password123');
      
      mockPrisma.user.findUnique.mockResolvedValue(null);
      bcrypt.hash.mockResolvedValue('hashed_password');
      mockPrisma.user.create.mockResolvedValue({
        id: 1,
        name: 'John Doe',
        email: 'john@example.com',
        password: 'hashed_password',
        createdAt: new Date(),
        updatedAt: new Date()
      });
      mockJwtService.generateToken.mockReturnValue('mock_jwt_token');
      mockEmailService.sendWelcomeEmail.mockRejectedValue(new Error('Email service error'));

      // Act
      const result = await handler.handle(command);

      // Assert - should still return user and token even if email fails
      expect(result.user.email).toBe('john@example.com');
      expect(result.token).toBe('mock_jwt_token');
    });
  });
});
