import { describe, it, expect, beforeEach, vi } from 'vitest';
import { UserService } from '../../src/services/user.service';
import { UserEntity } from '../../src/entities/user.entity';
import { Role } from '../../src/entities/role.entity';
import { DataSource, Repository } from 'typeorm';

// Mock DataSource
const mockDataSource = {
  getRepository: vi.fn()
} as unknown as DataSource;

// Mock repositories
const mockUserRepository = {
  create: vi.fn(),
  save: vi.fn(),
  findOne: vi.fn(),
  update: vi.fn(),
  delete: vi.fn(),
  find: vi.fn(),
  findByIds: vi.fn()
} as unknown as Repository<UserEntity>;

const mockRoleRepository = {
  findByIds: vi.fn()
} as unknown as Repository<Role>;

describe('UserService', () => {
  let userService: UserService;

  beforeEach(() => {
    vi.clearAllMocks();

    // Setup mock repositories
    vi.mocked(mockDataSource.getRepository)
      .mockReturnValueOnce(mockUserRepository)
      .mockReturnValueOnce(mockRoleRepository);

    userService = new UserService(mockDataSource);
  });

  describe('createUser', () => {
    it('should create a user with hashed password', async () => {
      const userData = {
        username: 'testuser',
        password: 'password123',
        email: 'test@example.com'
      };

      const mockUser = {
        id: 1,
        ...userData,
        password: 'hashed_password',
        phone: null,
        nickname: null,
        name: null,
        avatarFileId: null,
        disabledStatus: 'enabled',
        deleteStatus: 'active',
        createdAt: new Date(),
        updatedAt: new Date(),
        roles: []
      } as unknown as UserEntity;

      vi.mocked(mockUserRepository.create).mockReturnValue(mockUser);
      vi.mocked(mockUserRepository.save).mockResolvedValue(mockUser);

      const result = await userService.createUser(userData);

      expect(mockUserRepository.create).toHaveBeenCalledWith({
        ...userData,
        password: expect.not.stringMatching('password123') // Password should be hashed
      });
      expect(mockUserRepository.save).toHaveBeenCalledWith(mockUser);
      expect(result).toEqual(mockUser);
    });

    it('should throw error when creation fails', async () => {
      const userData = {
        username: 'testuser',
        password: 'password123'
      };

      vi.mocked(mockUserRepository.create).mockImplementation(() => {
        throw new Error('Database error');
      });

      await expect(userService.createUser(userData)).rejects.toThrow('Failed to create user');
    });
  });

  describe('getUserById', () => {
    it('should return user by id', async () => {
      const mockUser = {
        id: 1,
        username: 'testuser',
        password: 'hashedpassword',
        phone: null,
        email: 'test@example.com',
        nickname: null,
        name: null,
        avatarFileId: null,
        disabledStatus: 'enabled',
        deleteStatus: 'active',
        createdAt: new Date(),
        updatedAt: new Date(),
        roles: []
      } as unknown as UserEntity;

      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);

      const result = await userService.getUserById(1);

      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
        where: { id: 1 },
        relations: ['roles', 'avatarFile']
      });
      expect(result).toEqual(mockUser);
    });

    it('should return null when user not found', async () => {
      vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);

      const result = await userService.getUserById(999);

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

  describe('getUserByUsername', () => {
    it('should return user by username', async () => {
      const mockUser = {
        id: 1,
        username: 'testuser',
        password: 'hashedpassword',
        phone: null,
        email: 'test@example.com',
        nickname: null,
        name: null,
        avatarFileId: null,
        disabledStatus: 'enabled',
        deleteStatus: 'active',
        createdAt: new Date(),
        updatedAt: new Date(),
        roles: []
      } as unknown as UserEntity;

      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);

      const result = await userService.getUserByUsername('testuser');

      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
        where: { username: 'testuser' },
        relations: ['roles', 'avatarFile']
      });
      expect(result).toEqual(mockUser);
    });
  });

  describe('updateUser', () => {
    it('should update user with hashed password', async () => {
      const updateData = {
        password: 'newpassword',
        email: 'new@example.com'
      };

      const updatedUser = {
        id: 1,
        username: 'testuser',
        password: 'hashed_newpassword',
        email: 'new@example.com',
        roles: []
      } as unknown as UserEntity;

      vi.mocked(mockUserRepository.update).mockResolvedValue({ affected: 1 } as any);
      vi.mocked(mockUserRepository.findOne).mockResolvedValue(updatedUser);

      const result = await userService.updateUser(1, updateData);

      expect(mockUserRepository.update).toHaveBeenCalledWith(1, {
        ...updateData,
        password: expect.not.stringMatching('newpassword') // Password should be hashed
      });
      expect(result).toEqual(updatedUser);
    });
  });

  describe('deleteUser', () => {
    it('should delete user successfully', async () => {
      vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 1 } as any);

      const result = await userService.deleteUser(1);

      expect(mockUserRepository.delete).toHaveBeenCalledWith(1);
      expect(result).toBe(true);
    });

    it('should return false when user not found', async () => {
      vi.mocked(mockUserRepository.delete).mockResolvedValue({ affected: 0 } as any);

      const result = await userService.deleteUser(999);

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

  describe('assignRoles', () => {
    it('should assign roles to user', async () => {
      const mockUser = {
        id: 1,
        username: 'testuser',
        password: 'hashedpassword',
        phone: null,
        email: 'test@example.com',
        nickname: null,
        name: null,
        avatarFileId: null,
        disabledStatus: 'enabled',
        deleteStatus: 'active',
        createdAt: new Date(),
        updatedAt: new Date(),
        roles: []
      } as unknown as UserEntity;

      const mockRoles = [
        { id: 1, name: 'admin' } as Role,
        { id: 2, name: 'user' } as Role
      ];

      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);
      vi.mocked(mockRoleRepository.findByIds).mockResolvedValue(mockRoles);
      vi.mocked(mockUserRepository.save).mockResolvedValue({
        ...mockUser,
        roles: mockRoles
      } as UserEntity);

      const result = await userService.assignRoles(1, [1, 2]);

      expect(mockRoleRepository.findByIds).toHaveBeenCalledWith([1, 2]);
      expect(mockUserRepository.save).toHaveBeenCalledWith({
        ...mockUser,
        roles: mockRoles
      });
      expect(result?.roles).toEqual(mockRoles);
    });

    it('should return null when user not found', async () => {
      vi.mocked(mockUserRepository.findOne).mockResolvedValue(null);

      const result = await userService.assignRoles(999, [1, 2]);

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

  describe('getUsers', () => {
    it('should return all users', async () => {
      const mockUsers = [
        { id: 1, username: 'user1', roles: [] },
        { id: 2, username: 'user2', roles: [] }
      ] as unknown as UserEntity[];

      vi.mocked(mockUserRepository.find).mockResolvedValue(mockUsers);

      const result = await userService.getUsers();

      expect(mockUserRepository.find).toHaveBeenCalledWith({
        relations: ['roles', 'avatarFile']
      });
      expect(result).toEqual(mockUsers);
    });
  });

  describe('getUserByAccount', () => {
    it('should return user by username or email', async () => {
      const mockUser = {
        id: 1,
        username: 'testuser',
        email: 'test@example.com',
        roles: []
      } as unknown as UserEntity;

      vi.mocked(mockUserRepository.findOne).mockResolvedValue(mockUser);

      const result = await userService.getUserByAccount('testuser');

      expect(mockUserRepository.findOne).toHaveBeenCalledWith({
        where: [{ username: 'testuser' }, { email: 'testuser' }],
        relations: ['roles', 'avatarFile']
      });
      expect(result).toEqual(mockUser);
    });
  });
});