const UserController = require('../../../src/api/controllers/UserController');
const GetMeQuery = require('../../../src/application/user/queries/GetMeQuery');
const GetAllUsersQuery = require('../../../src/application/user/queries/GetAllUsersQuery');
const GetUserByIdQuery = require('../../../src/application/user/queries/GetUserByIdQuery');
const UpdateUserProfileCommand = require('../../../src/application/user/commands/UpdateUserProfileCommand');

describe('UserController', () => {
  let controller;
  let mockGetMeHandler;
  let mockGetAllUsersHandler;
  let mockGetUserByIdHandler;
  let mockUpdateProfileHandler;
  let mockReq;
  let mockRes;

  beforeEach(() => {
    // Mock handlers
    mockGetMeHandler = { handle: jest.fn() };
    mockGetAllUsersHandler = { handle: jest.fn() };
    mockGetUserByIdHandler = { handle: jest.fn() };
    mockUpdateProfileHandler = { handle: jest.fn() };

    controller = new UserController(
      mockGetMeHandler,
      mockGetAllUsersHandler,
      mockGetUserByIdHandler,
      mockUpdateProfileHandler
    );

    // Mock Express req/res
    mockReq = {
      user: { userId: 1 }, // Set by authMiddleware
      body: {},
      params: {}
    };
    mockRes = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn()
    };

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

  describe('getMe', () => {
    it('should return current user successfully', async () => {
      // Arrange
      const mockUser = {
        id: 1,
        name: 'John Doe',
        email: 'john@example.com'
      };

      mockGetMeHandler.handle.mockResolvedValue(mockUser);

      // Act
      await controller.getMe(mockReq, mockRes);

      // Assert
      expect(mockGetMeHandler.handle).toHaveBeenCalledWith(
        expect.any(GetMeQuery)
      );
      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        message: 'User retrieved successfully',
        data: mockUser
      });
    });

    it('should return 404 if user not found', async () => {
      // Arrange
      mockGetMeHandler.handle.mockRejectedValue(new Error('User not found'));

      // Act
      await controller.getMe(mockReq, mockRes);

      // Assert
      expect(mockRes.status).toHaveBeenCalledWith(404);
      expect(mockRes.json).toHaveBeenCalledWith({
        error: 'User not found'
      });
    });
  });

  describe('getAll', () => {
    it('should return all users successfully', async () => {
      // Arrange
      const mockUsers = [
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Doe', email: 'jane@example.com' }
      ];

      mockGetAllUsersHandler.handle.mockResolvedValue(mockUsers);

      // Act
      await controller.getAll(mockReq, mockRes);

      // Assert
      expect(mockGetAllUsersHandler.handle).toHaveBeenCalledWith(
        expect.any(GetAllUsersQuery)
      );
      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        message: 'Users retrieved successfully',
        data: mockUsers,
        count: 2
      });
    });

    it('should return empty array if no users exist', async () => {
      // Arrange
      mockGetAllUsersHandler.handle.mockResolvedValue([]);

      // Act
      await controller.getAll(mockReq, mockRes);

      // Assert
      expect(mockRes.json).toHaveBeenCalledWith({
        message: 'Users retrieved successfully',
        data: [],
        count: 0
      });
    });
  });

  describe('getById', () => {
    it('should return user by ID successfully', async () => {
      // Arrange
      mockReq.params = { id: '2' };
      const mockUser = {
        id: 2,
        name: 'Jane Doe',
        email: 'jane@example.com'
      };

      mockGetUserByIdHandler.handle.mockResolvedValue(mockUser);

      // Act
      await controller.getById(mockReq, mockRes);

      // Assert
      expect(mockGetUserByIdHandler.handle).toHaveBeenCalledWith(
        expect.any(GetUserByIdQuery)
      );
      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        message: 'User retrieved successfully',
        data: mockUser
      });
    });

    it('should return 400 for invalid user ID', async () => {
      // Arrange
      mockReq.params = { id: 'invalid' };

      // Act
      await controller.getById(mockReq, mockRes);

      // Assert
      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        error: 'Invalid user ID'
      });
    });

    it('should return 404 if user not found', async () => {
      // Arrange
      mockReq.params = { id: '999' };
      mockGetUserByIdHandler.handle.mockRejectedValue(new Error('User not found'));

      // Act
      await controller.getById(mockReq, mockRes);

      // Assert
      expect(mockRes.status).toHaveBeenCalledWith(404);
      expect(mockRes.json).toHaveBeenCalledWith({
        error: 'User not found'
      });
    });
  });

  describe('updateMe', () => {
    it('should update user profile successfully', async () => {
      // Arrange
      mockReq.body = { name: 'John Updated' };
      const mockUpdatedUser = {
        id: 1,
        name: 'John Updated',
        email: 'john@example.com'
      };

      mockUpdateProfileHandler.handle.mockResolvedValue(mockUpdatedUser);

      // Act
      await controller.updateMe(mockReq, mockRes);

      // Assert
      expect(mockUpdateProfileHandler.handle).toHaveBeenCalledWith(
        expect.any(UpdateUserProfileCommand)
      );
      expect(mockRes.status).toHaveBeenCalledWith(200);
      expect(mockRes.json).toHaveBeenCalledWith({
        message: 'Profile updated successfully',
        data: mockUpdatedUser
      });
    });

    it('should return 400 if name is missing', async () => {
      // Arrange
      mockReq.body = { name: '' };
      mockUpdateProfileHandler.handle.mockRejectedValue(new Error('Name is required'));

      // Act
      await controller.updateMe(mockReq, mockRes);

      // Assert
      expect(mockRes.status).toHaveBeenCalledWith(400);
      expect(mockRes.json).toHaveBeenCalledWith({
        error: 'Name is required'
      });
    });
  });
});
