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 {
  useStaff,
  useUpdateStaff,
  useToggleStaffActive,
} from '../useStaff';
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('useStaff hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useStaff', () => {
    it('fetches staff and transforms data correctly', async () => {
      const mockStaff = [
        {
          id: 1,
          name: 'John Doe',
          email: 'john@example.com',
          phone: '555-1234',
          role: 'TENANT_MANAGER',
          is_active: true,
          permissions: { can_invite_staff: true },
          can_invite_staff: true,
        },
        {
          id: 2,
          name: 'Jane Smith',
          email: 'jane@example.com',
          phone: '555-5678',
          role: 'TENANT_STAFF',
          is_active: false,
          permissions: {},
          can_invite_staff: false,
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/staff/?show_inactive=true');
      expect(result.current.data).toHaveLength(2);
      expect(result.current.data?.[0]).toEqual({
        id: '1',
        name: 'John Doe',
        email: 'john@example.com',
        phone: '555-1234',
        role: 'TENANT_MANAGER',
        is_active: true,
        permissions: { can_invite_staff: true },
        can_invite_staff: true,
      });
      expect(result.current.data?.[1]).toEqual({
        id: '2',
        name: 'Jane Smith',
        email: 'jane@example.com',
        phone: '555-5678',
        role: 'TENANT_STAFF',
        is_active: false,
        permissions: {},
        can_invite_staff: false,
      });
    });

    it('applies search filter', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: [] });

      renderHook(() => useStaff({ search: 'john' }), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(apiClient.get).toHaveBeenCalledWith('/staff/?search=john&show_inactive=true');
      });
    });

    it('transforms name from first_name and last_name when name is missing', async () => {
      const mockStaff = [
        {
          id: 1,
          first_name: 'John',
          last_name: 'Doe',
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(result.current.data?.[0].name).toBe('John Doe');
    });

    it('falls back to email when name and first/last name are missing', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

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

    it('handles partial first/last name correctly', async () => {
      const mockStaff = [
        {
          id: 1,
          first_name: 'John',
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
        {
          id: 2,
          last_name: 'Smith',
          email: 'smith@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(result.current.data?.[0].name).toBe('John');
      expect(result.current.data?.[1].name).toBe('Smith');
    });

    it('defaults is_active to true when missing', async () => {
      const mockStaff = [
        {
          id: 1,
          name: 'John Doe',
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(result.current.data?.[0].is_active).toBe(true);
    });

    it('defaults can_invite_staff to false when missing', async () => {
      const mockStaff = [
        {
          id: 1,
          name: 'John Doe',
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(result.current.data?.[0].can_invite_staff).toBe(false);
    });

    it('handles empty phone and sets defaults for missing fields', async () => {
      const mockStaff = [
        {
          id: 1,
          email: 'john@example.com',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

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

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

      expect(result.current.data?.[0]).toEqual({
        id: '1',
        name: 'john@example.com',
        email: 'john@example.com',
        phone: '',
        role: 'staff',
        is_active: true,
        permissions: {},
        can_invite_staff: false,
      });
    });

    it('converts id to string', async () => {
      const mockStaff = [
        {
          id: 123,
          name: 'John Doe',
          email: 'john@example.com',
          role: 'TENANT_STAFF',
        },
      ];
      vi.mocked(apiClient.get).mockResolvedValue({ data: mockStaff });

      const { result } = renderHook(() => useStaff(), {
        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('does not retry on failure', async () => {
      vi.mocked(apiClient.get).mockRejectedValue(new Error('Network error'));

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

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

      // Should only be called once (no retries)
      expect(apiClient.get).toHaveBeenCalledTimes(1);
    });
  });

  describe('useUpdateStaff', () => {
    it('updates staff member with is_active', async () => {
      const mockResponse = {
        id: 1,
        is_active: false,
        permissions: {},
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });

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

      await act(async () => {
        await result.current.mutateAsync({
          id: '1',
          updates: { is_active: false },
        });
      });

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

    it('updates staff member with permissions', async () => {
      const mockResponse = {
        id: 1,
        permissions: { can_invite_staff: true },
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });

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

      await act(async () => {
        await result.current.mutateAsync({
          id: '2',
          updates: { permissions: { can_invite_staff: true } },
        });
      });

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

    it('updates staff member with both is_active and permissions', async () => {
      const mockResponse = {
        id: 1,
        is_active: true,
        permissions: { can_invite_staff: false },
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });

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

      await act(async () => {
        await result.current.mutateAsync({
          id: '3',
          updates: {
            is_active: true,
            permissions: { can_invite_staff: false },
          },
        });
      });

      expect(apiClient.patch).toHaveBeenCalledWith('/staff/3/', {
        is_active: true,
        permissions: { can_invite_staff: false },
      });
    });

    it('invalidates staff queries on success', async () => {
      vi.mocked(apiClient.patch).mockResolvedValue({ data: {} });

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });
      const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');

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

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

      await act(async () => {
        await result.current.mutateAsync({
          id: '1',
          updates: { is_active: false },
        });
      });

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['staff'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['businessUsers'] });
    });

    it('returns response data', async () => {
      const mockResponse = {
        id: 1,
        name: 'John Doe',
        is_active: false,
      };
      vi.mocked(apiClient.patch).mockResolvedValue({ data: mockResponse });

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

      let responseData;
      await act(async () => {
        responseData = await result.current.mutateAsync({
          id: '1',
          updates: { is_active: false },
        });
      });

      expect(responseData).toEqual(mockResponse);
    });
  });

  describe('useToggleStaffActive', () => {
    it('toggles staff member active status', async () => {
      const mockResponse = {
        id: 1,
        is_active: false,
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

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

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

      expect(apiClient.post).toHaveBeenCalledWith('/staff/1/toggle_active/');
    });

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

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

      await act(async () => {
        await result.current.mutateAsync('42');
      });

      expect(apiClient.post).toHaveBeenCalledWith('/staff/42/toggle_active/');
    });

    it('invalidates staff queries on success', async () => {
      vi.mocked(apiClient.post).mockResolvedValue({ data: {} });

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });
      const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');

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

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

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['staff'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['businessUsers'] });
    });

    it('returns response data', async () => {
      const mockResponse = {
        id: 1,
        name: 'John Doe',
        is_active: true,
      };
      vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });

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

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

      expect(responseData).toEqual(mockResponse);
    });

    it('handles API errors', async () => {
      const errorMessage = 'Staff member not found';
      vi.mocked(apiClient.post).mockRejectedValue(new Error(errorMessage));

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

      let caughtError: Error | null = null;
      await act(async () => {
        try {
          await result.current.mutateAsync('999');
        } catch (error) {
          caughtError = error as Error;
        }
      });

      expect(caughtError).toBeInstanceOf(Error);
      expect(caughtError?.message).toBe(errorMessage);
      expect(apiClient.post).toHaveBeenCalledWith('/staff/999/toggle_active/');
    });
  });
});
