import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useUsers, useRoles, useCreateUser, useUpdateUser, useDeleteUser, userKeys } from '../use-users';
import { userService } from '@/lib/services/user.service';
import { toast } from 'sonner';

// Mock the service
vi.mock('@/lib/services/user.service', () => ({
  userService: {
    getAll: vi.fn(),
    getByUuid: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    delete: vi.fn(),
    getRoles: vi.fn(),
  },
}));

describe('use-users hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('userKeys', () => {
    it('should generate correct cache keys', () => {
      expect(userKeys.all).toEqual(['users']);
      expect(userKeys.list({ search: 'john' })).toEqual(['users', 'list', { search: 'john' }]);
      expect(userKeys.detail('uuid-1')).toEqual(['users', 'detail', 'uuid-1']);
    });
  });

  describe('useUsers', () => {
    it('should fetch users successfully', async () => {
      const mockData = {
        data: [
          { userId: 1, username: 'john', email: 'john@example.com' },
          { userId: 2, username: 'jane', email: 'jane@example.com' },
        ],
        meta: { total: 2, page: 1, limit: 10 },
      };

      vi.mocked(userService.getAll).mockResolvedValue(mockData);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUsers({ search: 'test' }), { wrapper });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockData);
      expect(userService.getAll).toHaveBeenCalledWith({ search: 'test' });
    });

    it('should handle error state', async () => {
      vi.mocked(userService.getAll).mockRejectedValue(new Error('API Error'));

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUsers(), { wrapper });

      await waitFor(() => {
        expect(result.current.isError).toBe(true);
      });
    });
  });

  describe('useRoles', () => {
    it('should fetch roles successfully', async () => {
      const mockRoles = [
        { roleId: 1, name: 'Admin' },
        { roleId: 2, name: 'Editor' },
        { roleId: 3, name: 'Viewer' },
      ];

      vi.mocked(userService.getRoles).mockResolvedValue(mockRoles);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useRoles(), { wrapper });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data).toEqual(mockRoles);
      expect(userService.getRoles).toHaveBeenCalled();
    });
  });

  describe('useCreateUser', () => {
    it('should create user and show success toast', async () => {
      const mockResponse = { userId: 1, username: 'newuser' };
      vi.mocked(userService.create).mockResolvedValue(mockResponse);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useCreateUser(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          username: 'newuser',
          email: 'newuser@example.com',
          password: 'password123',
          roleIds: [2],
        });
      });

      expect(userService.create).toHaveBeenCalled();
      expect(toast.success).toHaveBeenCalledWith('User created successfully');
    });

    it('should show error toast on failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'Username already exists' } },
      };
      vi.mocked(userService.create).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useCreateUser(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync({
            username: 'existinguser',
            email: 'test@example.com',
            password: 'password',
          });
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to create user', {
        description: 'Username already exists',
      });
    });
  });

  describe('useUpdateUser', () => {
    it('should update user and show success toast', async () => {
      const mockResponse = { userId: 1, email: 'updated@example.com' };
      vi.mocked(userService.update).mockResolvedValue(mockResponse);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUpdateUser(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          uuid: 'uuid-1',
          data: { email: 'updated@example.com' },
        });
      });

      expect(userService.update).toHaveBeenCalledWith('uuid-1', { email: 'updated@example.com' });
      expect(toast.success).toHaveBeenCalledWith('User updated successfully');
    });

    it('should show error toast on failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'User not found' } },
      };
      vi.mocked(userService.update).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useUpdateUser(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync({
            uuid: 'uuid-999',
            data: { email: 'test@example.com' },
          });
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to update user', {
        description: 'User not found',
      });
    });
  });

  describe('useDeleteUser', () => {
    it('should delete user and show success toast', async () => {
      vi.mocked(userService.delete).mockResolvedValue({});

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useDeleteUser(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync('uuid-1');
      });

      expect(userService.delete).toHaveBeenCalledWith('uuid-1');
      expect(toast.success).toHaveBeenCalledWith('User deleted successfully');
    });

    it('should show error toast on delete failure', async () => {
      const mockError = {
        message: 'Error',
        response: { data: { message: 'Cannot delete yourself' } },
      };
      vi.mocked(userService.delete).mockRejectedValue(mockError);

      const { wrapper } = createTestQueryClient();
      const { result } = renderHook(() => useDeleteUser(), { wrapper });

      await act(async () => {
        try {
          await result.current.mutateAsync('uuid-1');
        } catch {
          // Expected
        }
      });

      expect(toast.error).toHaveBeenCalledWith('Failed to delete user', {
        description: 'Cannot delete yourself',
      });
    });
  });
});
