import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

// Mock apiClient
vi.mock('../../api/client', () => ({
  default: {
    get: vi.fn(),
    post: vi.fn(),
    patch: vi.fn(),
  },
}));

import {
  useUsers,
  useStaffForAssignment,
  usePlatformStaffForAssignment,
  useUpdateStaffPermissions,
} from '../useUsers';
import apiClient from '../../api/client';

// Create wrapper
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return function Wrapper({ children }: { children: React.ReactNode }) {
    return React.createElement(QueryClientProvider, { client: queryClient }, children);
  };
};

describe('useUsers hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useUsers', () => {
    it('fetches all staff members', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'owner@example.com',
          name: 'John Owner',
          username: 'jowner',
          role: 'owner',
          is_active: true,
          permissions: { can_access_resources: true },
          can_invite_staff: true,
        },
        {
          id: 2,
          email: 'manager@example.com',
          name: 'Jane Manager',
          username: 'jmanager',
          role: 'manager',
          is_active: true,
          permissions: { can_access_services: false },
          can_invite_staff: false,
        },
        {
          id: 3,
          email: 'staff@example.com',
          name: 'Bob Staff',
          username: 'bstaff',
          role: 'staff',
          is_active: false,
          permissions: {},
          can_invite_staff: false,
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/staff/');
      expect(result.current.data).toHaveLength(3);
      expect(result.current.data).toEqual(mockStaff);
    });

    it('returns empty array when no staff members exist', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: [] });

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/staff/');
      expect(result.current.data).toEqual([]);
    });

    it('handles API errors', async () => {
      const errorMessage = 'Failed to fetch staff';
      vi.mocked(apiClient.get).mockRejectedValue(new Error(errorMessage));

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

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

      expect(result.current.error).toBeDefined();
    });

    it('uses correct query key', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: [] });

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

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

      // Query key should be ['staff'] for caching and invalidation
      expect(apiClient.get).toHaveBeenCalledWith('/staff/');
    });
  });

  describe('useStaffForAssignment', () => {
    it('fetches and transforms staff for dropdown use', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'john@example.com',
          name: 'John Doe',
          role: 'owner',
          is_active: true,
          permissions: {},
        },
        {
          id: 2,
          email: 'jane@example.com',
          name: 'Jane Smith',
          role: 'manager',
          is_active: true,
          permissions: {},
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

      const { result } = renderHook(() => useStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(apiClient.get).toHaveBeenCalledWith('/staff/');
      expect(result.current.data).toEqual([
        {
          id: '1',
          name: 'John Doe',
          email: 'john@example.com',
          role: 'owner',
        },
        {
          id: '2',
          name: 'Jane Smith',
          email: 'jane@example.com',
          role: 'manager',
        },
      ]);
    });

    it('converts id to string', async () => {
      const mockStaff = [
        {
          id: 123,
          email: 'test@example.com',
          name: 'Test User',
          role: 'staff',
          is_active: true,
          permissions: {},
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

      const { result } = renderHook(() => useStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data?.[0].id).toBe('123');
      expect(typeof result.current.data?.[0].id).toBe('string');
    });

    it('falls back to email when name is not provided', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'noname@example.com',
          name: null,
          role: 'staff',
          is_active: true,
          permissions: {},
        },
        {
          id: 2,
          email: 'emptyname@example.com',
          name: '',
          role: 'staff',
          is_active: true,
          permissions: {},
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

      const { result } = renderHook(() => useStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data?.[0].name).toBe('noname@example.com');
      expect(result.current.data?.[1].name).toBe('emptyname@example.com');
    });

    it('includes all roles (owner, manager, staff)', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'owner@example.com',
          name: 'Owner User',
          role: 'owner',
          is_active: true,
          permissions: {},
        },
        {
          id: 2,
          email: 'manager@example.com',
          name: 'Manager User',
          role: 'manager',
          is_active: true,
          permissions: {},
        },
        {
          id: 3,
          email: 'staff@example.com',
          name: 'Staff User',
          role: 'staff',
          is_active: true,
          permissions: {},
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

      const { result } = renderHook(() => useStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data).toHaveLength(3);
      expect(result.current.data?.map(u => u.role)).toEqual(['owner', 'manager', 'staff']);
    });

    it('returns empty array when no staff exist', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: [] });

      const { result } = renderHook(() => useStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data).toEqual([]);
    });
  });

  describe('usePlatformStaffForAssignment', () => {
    it('fetches and filters platform staff by role', async () => {
      const mockPlatformUsers = [
        {
          id: 1,
          email: 'super@platform.com',
          name: 'Super User',
          role: 'superuser',
        },
        {
          id: 2,
          email: 'manager@platform.com',
          name: 'Platform Manager',
          role: 'platform_manager',
        },
        {
          id: 3,
          email: 'support@platform.com',
          name: 'Platform Support',
          role: 'platform_support',
        },
        {
          id: 4,
          email: 'owner@business.com',
          name: 'Business Owner',
          role: 'owner',
        },
        {
          id: 5,
          email: 'staff@business.com',
          name: 'Business Staff',
          role: 'staff',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(apiClient.get).toHaveBeenCalledWith('/platform/users/');

      // Should only return platform roles
      expect(result.current.data).toHaveLength(3);
      expect(result.current.data?.map(u => u.role)).toEqual([
        'superuser',
        'platform_manager',
        'platform_support',
      ]);
    });

    it('transforms platform users for dropdown use', async () => {
      const mockPlatformUsers = [
        {
          id: 10,
          email: 'admin@platform.com',
          name: 'Admin User',
          role: 'superuser',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data).toEqual([
        {
          id: '10',
          name: 'Admin User',
          email: 'admin@platform.com',
          role: 'superuser',
        },
      ]);
    });

    it('filters out non-platform roles', async () => {
      const mockPlatformUsers = [
        { id: 1, email: 'super@platform.com', name: 'Super', role: 'superuser' },
        { id: 2, email: 'owner@business.com', name: 'Owner', role: 'owner' },
        { id: 3, email: 'manager@business.com', name: 'Manager', role: 'manager' },
        { id: 4, email: 'staff@business.com', name: 'Staff', role: 'staff' },
        { id: 5, email: 'resource@business.com', name: 'Resource', role: 'resource' },
        { id: 6, email: 'customer@business.com', name: 'Customer', role: 'customer' },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      // Only superuser should be included from the mock data
      expect(result.current.data).toHaveLength(1);
      expect(result.current.data?.[0].role).toBe('superuser');
    });

    it('includes all three platform roles', async () => {
      const mockPlatformUsers = [
        { id: 1, email: 'super@platform.com', name: 'Super', role: 'superuser' },
        { id: 2, email: 'pm@platform.com', name: 'PM', role: 'platform_manager' },
        { id: 3, email: 'support@platform.com', name: 'Support', role: 'platform_support' },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      const roles = result.current.data?.map(u => u.role);
      expect(roles).toContain('superuser');
      expect(roles).toContain('platform_manager');
      expect(roles).toContain('platform_support');
    });

    it('falls back to email when name is missing', async () => {
      const mockPlatformUsers = [
        {
          id: 1,
          email: 'noname@platform.com',
          role: 'superuser',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data?.[0].name).toBe('noname@platform.com');
    });

    it('returns empty array when no platform users exist', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: [] });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data).toEqual([]);
    });

    it('returns empty array when only non-platform users exist', async () => {
      const mockPlatformUsers = [
        { id: 1, email: 'owner@business.com', name: 'Owner', role: 'owner' },
        { id: 2, email: 'staff@business.com', name: 'Staff', role: 'staff' },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPlatformUsers });

      const { result } = renderHook(() => usePlatformStaffForAssignment(), {
        wrapper: createWrapper(),
      });

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

      expect(result.current.data).toEqual([]);
    });
  });

  describe('useUpdateStaffPermissions', () => {
    it('updates staff permissions', async () => {
      const updatedStaff = {
        id: 5,
        email: 'staff@example.com',
        name: 'Staff User',
        role: 'staff',
        is_active: true,
        permissions: {
          can_access_resources: true,
          can_access_services: false,
        },
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: updatedStaff });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({
          userId: 5,
          permissions: {
            can_access_resources: true,
            can_access_services: false,
          },
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/5/', {
        permissions: {
          can_access_resources: true,
          can_access_services: false,
        },
      });
    });

    it('accepts string userId', async () => {
      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({
          userId: '42',
          permissions: { can_access_resources: true },
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/42/', {
        permissions: { can_access_resources: true },
      });
    });

    it('accepts number userId', async () => {
      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({
          userId: 123,
          permissions: { can_list_customers: true },
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/123/', {
        permissions: { can_list_customers: true },
      });
    });

    it('can update multiple permissions at once', async () => {
      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      const permissions = {
        can_access_resources: true,
        can_access_services: true,
        can_list_customers: false,
        can_access_scheduled_tasks: false,
      };

      await act(async () => {
        await result.current.mutateAsync({
          userId: 1,
          permissions,
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/1/', {
        permissions,
      });
    });

    it('can set permissions to empty object', async () => {
      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        await result.current.mutateAsync({
          userId: 1,
          permissions: {},
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/1/', {
        permissions: {},
      });
    });

    it('invalidates staff query on success', async () => {
      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');

      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

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

      await act(async () => {
        await result.current.mutateAsync({
          userId: 1,
          permissions: { can_access_resources: true },
        });
      });

      await waitFor(() => {
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['staff'] });
      });
    });

    it('handles API errors', async () => {
      const errorMessage = 'Permission update failed';
      vi.mocked(apiClient.patch).mockRejectedValue(new Error(errorMessage));

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        try {
          await result.current.mutateAsync({
            userId: 1,
            permissions: { can_access_resources: true },
          });
        } catch (error) {
          expect(error).toBeDefined();
        }
      });

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

    it('returns updated data from mutation', async () => {
      const updatedStaff = {
        id: 10,
        email: 'updated@example.com',
        name: 'Updated User',
        role: 'staff',
        is_active: true,
        permissions: {
          can_access_resources: true,
        },
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: updatedStaff });

      const { result } = renderHook(() => useUpdateStaffPermissions(), {
        wrapper: createWrapper(),
      });

      let mutationResult;
      await act(async () => {
        mutationResult = await result.current.mutateAsync({
          userId: 10,
          permissions: { can_access_resources: true },
        });
      });

      expect(mutationResult).toEqual(updatedStaff);
    });
  });
});
