/**
 * Unit tests for useTimeBlocks hook
 *
 * Tests all time block and holiday management hooks including:
 * - Query hooks for fetching time blocks, blocked dates, and holidays
 * - Mutation hooks for creating, updating, deleting, and toggling time blocks
 * - Approval workflow hooks for pending reviews, approving, and denying
 * - Conflict checking functionality
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React, { type ReactNode } from 'react';
import apiClient from '../../api/client';
import {
  useTimeBlocks,
  useTimeBlock,
  useBlockedDates,
  useMyBlocks,
  useCreateTimeBlock,
  useUpdateTimeBlock,
  useDeleteTimeBlock,
  useToggleTimeBlock,
  usePendingReviews,
  useApproveTimeBlock,
  useDenyTimeBlock,
  useCheckConflicts,
  useHolidays,
  useHoliday,
  useHolidayDates,
  TimeBlockFilters,
  BlockedDatesParams,
  CreateTimeBlockData,
  CheckConflictsData,
} from '../useTimeBlocks';
import {
  TimeBlock,
  TimeBlockListItem,
  BlockedDate,
  Holiday,
  TimeBlockConflictCheck,
  MyBlocksResponse,
} from '../../types';

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

// Test data factories
const createMockTimeBlockListItem = (overrides?: Partial<TimeBlockListItem>): TimeBlockListItem => ({
  id: '1',
  title: 'Test Block',
  description: 'Test description',
  resource: null,
  resource_name: undefined,
  level: 'business',
  block_type: 'HARD',
  recurrence_type: 'NONE',
  start_date: '2025-01-01',
  end_date: '2025-01-02',
  all_day: true,
  is_active: true,
  created_at: '2025-01-01T00:00:00Z',
  approval_status: 'APPROVED',
  ...overrides,
});

const createMockTimeBlock = (overrides?: Partial<TimeBlock>): TimeBlock => ({
  id: '1',
  title: 'Test Block',
  description: 'Test description',
  resource: null,
  resource_name: undefined,
  level: 'business',
  block_type: 'HARD',
  recurrence_type: 'NONE',
  start_date: '2025-01-01',
  end_date: '2025-01-02',
  all_day: true,
  is_active: true,
  created_at: '2025-01-01T00:00:00Z',
  ...overrides,
});

const createMockBlockedDate = (overrides?: Partial<BlockedDate>): BlockedDate => ({
  date: '2025-01-01',
  block_type: 'HARD',
  title: 'Test Block',
  resource_id: null,
  all_day: true,
  start_time: null,
  end_time: null,
  time_block_id: '1',
  ...overrides,
});

const createMockHoliday = (overrides?: Partial<Holiday>): Holiday => ({
  code: 'new-year',
  name: 'New Year\'s Day',
  country: 'US',
  holiday_type: 'FIXED',
  month: 1,
  day: 1,
  is_active: true,
  next_occurrence: '2025-01-01',
  ...overrides,
});

// Test wrapper with QueryClient
const createWrapper = (queryClient: QueryClient) => {
  return ({ children }: { children: ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

describe('useTimeBlocks', () => {
  let queryClient: QueryClient;

  beforeEach(() => {
    queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false, gcTime: 0 },
        mutations: { retry: false },
      },
    });
    vi.clearAllMocks();
  });

  afterEach(() => {
    queryClient.clear();
  });

  describe('Query Hooks', () => {
    describe('useTimeBlocks', () => {
      it('should fetch time blocks without filters', async () => {
        const mockBlocks = [
          createMockTimeBlockListItem({ id: '1', title: 'Block 1' }),
          createMockTimeBlockListItem({ id: '2', title: 'Block 2' }),
        ];

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockBlocks });

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

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

        expect(apiClient.get).toHaveBeenCalledWith('/time-blocks/?');
        expect(result.current.data).toEqual(mockBlocks);
      });

      it('should fetch time blocks with filters', async () => {
        const filters: TimeBlockFilters = {
          level: 'resource',
          resource_id: '123',
          block_type: 'SOFT',
          recurrence_type: 'WEEKLY',
          is_active: true,
        };

        const mockBlocks = [createMockTimeBlockListItem({ level: 'resource' })];
        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockBlocks });

        const { result } = renderHook(() => useTimeBlocks(filters), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith(
          '/time-blocks/?level=resource&resource_id=123&block_type=SOFT&recurrence_type=WEEKLY&is_active=true'
        );
      });

      it('should convert numeric IDs to strings', async () => {
        const mockData = [
          { id: 1, resource: 456, title: 'Block', level: 'resource', block_type: 'HARD', recurrence_type: 'NONE', all_day: true, is_active: true, created_at: '2025-01-01T00:00:00Z' },
        ];

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockData });

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

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

        expect(result.current.data?.[0].id).toBe('1');
        expect(result.current.data?.[0].resource).toBe('456');
      });

      it('should handle API errors', async () => {
        const error = new Error('API Error');
        vi.mocked(apiClient.get).mockRejectedValueOnce(error);

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

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

    describe('useTimeBlock', () => {
      it('should fetch a single time block', async () => {
        const mockBlock = createMockTimeBlock({ id: '123', title: 'Single Block' });
        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockBlock });

        const { result } = renderHook(() => useTimeBlock('123'), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith('/time-blocks/123/');
        expect(result.current.data).toEqual(mockBlock);
      });

      it('should convert numeric IDs to strings', async () => {
        const mockData = { id: 123, resource: 456, title: 'Block', level: 'resource', block_type: 'HARD', recurrence_type: 'NONE', all_day: true, is_active: true, created_at: '2025-01-01T00:00:00Z' };
        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockData });

        const { result } = renderHook(() => useTimeBlock('123'), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(result.current.data?.id).toBe('123');
        expect(result.current.data?.resource).toBe('456');
      });

      it('should not fetch when ID is empty', async () => {
        const { result } = renderHook(() => useTimeBlock(''), {
          wrapper: createWrapper(queryClient),
        });

        expect(result.current.status).toBe('pending');
        expect(apiClient.get).not.toHaveBeenCalled();
      });
    });

    describe('useBlockedDates', () => {
      it('should fetch blocked dates with required params', async () => {
        const params: BlockedDatesParams = {
          start_date: '2025-01-01',
          end_date: '2025-01-31',
        };

        const mockResponse = {
          blocked_dates: [
            createMockBlockedDate({ date: '2025-01-01' }),
            createMockBlockedDate({ date: '2025-01-15' }),
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

        const { result } = renderHook(() => useBlockedDates(params), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith(
          '/time-blocks/blocked_dates/?start_date=2025-01-01&end_date=2025-01-31'
        );
      });

      it('should include optional resource_id and include_business params', async () => {
        const params: BlockedDatesParams = {
          start_date: '2025-01-01',
          end_date: '2025-01-31',
          resource_id: '456',
          include_business: true,
        };

        const mockResponse = { blocked_dates: [] };
        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

        const { result } = renderHook(() => useBlockedDates(params), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith(
          '/time-blocks/blocked_dates/?start_date=2025-01-01&end_date=2025-01-31&resource_id=456&include_business=true'
        );
      });

      it('should convert numeric IDs to strings', async () => {
        const params: BlockedDatesParams = {
          start_date: '2025-01-01',
          end_date: '2025-01-31',
        };

        const mockResponse = {
          blocked_dates: [
            { date: '2025-01-01', block_type: 'HARD', title: 'Test', resource_id: 123, all_day: true, start_time: null, end_time: null, time_block_id: 456 },
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

        const { result } = renderHook(() => useBlockedDates(params), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(result.current.data?.[0].resource_id).toBe('123');
        expect(result.current.data?.[0].time_block_id).toBe('456');
      });

      it('should not fetch when dates are missing', async () => {
        const params = { start_date: '', end_date: '' };

        const { result } = renderHook(() => useBlockedDates(params), {
          wrapper: createWrapper(queryClient),
        });

        expect(result.current.status).toBe('pending');
        expect(apiClient.get).not.toHaveBeenCalled();
      });
    });

    describe('useMyBlocks', () => {
      it('should fetch blocks for current staff member', async () => {
        const mockResponse: MyBlocksResponse = {
          business_blocks: [createMockTimeBlockListItem({ id: '1', title: 'Business Block' })],
          my_blocks: [createMockTimeBlockListItem({ id: '2', title: 'My Block', resource: '123' })],
          resource_id: '123',
          resource_name: 'John Doe',
          can_self_approve: false,
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

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

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

        expect(apiClient.get).toHaveBeenCalledWith('/time-blocks/my_blocks/');
        expect(result.current.data).toEqual(mockResponse);
      });

      it('should convert numeric IDs to strings', async () => {
        const mockData = {
          business_blocks: [{ id: 1, resource: null, title: 'Block', level: 'business', block_type: 'HARD', recurrence_type: 'NONE', all_day: true, is_active: true, created_at: '2025-01-01T00:00:00Z' }],
          my_blocks: [{ id: 2, resource: 456, title: 'Block', level: 'resource', block_type: 'HARD', recurrence_type: 'NONE', all_day: true, is_active: true, created_at: '2025-01-01T00:00:00Z' }],
          resource_id: 789,
          resource_name: 'John Doe',
          can_self_approve: true,
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockData });

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

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

        expect(result.current.data?.business_blocks[0].id).toBe('1');
        expect(result.current.data?.my_blocks[0].id).toBe('2');
        expect(result.current.data?.my_blocks[0].resource).toBe('456');
        expect(result.current.data?.resource_id).toBe('789');
      });
    });

    describe('usePendingReviews', () => {
      it('should fetch pending reviews', async () => {
        const mockResponse = {
          count: 2,
          pending_blocks: [
            createMockTimeBlockListItem({ id: '1', title: 'Pending 1', approval_status: 'PENDING' }),
            createMockTimeBlockListItem({ id: '2', title: 'Pending 2', approval_status: 'PENDING' }),
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

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

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

        expect(apiClient.get).toHaveBeenCalledWith('/time-blocks/pending_reviews/');
        expect(result.current.data?.count).toBe(2);
        expect(result.current.data?.pending_blocks).toHaveLength(2);
      });

      it('should convert numeric IDs to strings', async () => {
        const mockData = {
          count: 1,
          pending_blocks: [
            { id: 123, resource: 456, title: 'Block', level: 'resource', block_type: 'HARD', recurrence_type: 'NONE', all_day: true, is_active: true, created_at: '2025-01-01T00:00:00Z', approval_status: 'PENDING' },
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockData });

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

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

        expect(result.current.data?.pending_blocks[0].id).toBe('123');
        expect(result.current.data?.pending_blocks[0].resource).toBe('456');
      });
    });
  });

  describe('Mutation Hooks', () => {
    describe('useCreateTimeBlock', () => {
      it('should create a time block', async () => {
        const createData: CreateTimeBlockData = {
          title: 'New Block',
          description: 'Test',
          resource: null,
          block_type: 'HARD',
          recurrence_type: 'NONE',
          start_date: '2025-01-01',
          end_date: '2025-01-02',
          all_day: true,
        };

        const mockResponse = createMockTimeBlock(createData);
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate(createData);

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/', {
          ...createData,
          resource: null,
        });
      });

      it('should parse string resource ID to integer', async () => {
        const createData: CreateTimeBlockData = {
          title: 'Resource Block',
          resource: '456',
          block_type: 'SOFT',
          recurrence_type: 'WEEKLY',
          recurrence_pattern: { days_of_week: [0, 2, 4] },
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: {} });

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

        result.current.mutate(createData);

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/', {
          ...createData,
          resource: 456,
        });
      });

      it('should invalidate queries on success', async () => {
        const createData: CreateTimeBlockData = {
          title: 'New Block',
          block_type: 'HARD',
          recurrence_type: 'NONE',
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: {} });

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

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

        result.current.mutate(createData);

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
      });

      it('should handle API errors', async () => {
        const createData: CreateTimeBlockData = {
          title: 'New Block',
          block_type: 'HARD',
          recurrence_type: 'NONE',
        };

        const error = new Error('Validation Error');
        vi.mocked(apiClient.post).mockRejectedValueOnce(error);

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

        result.current.mutate(createData);

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

    describe('useUpdateTimeBlock', () => {
      it('should update a time block', async () => {
        const updates: Partial<CreateTimeBlockData> = {
          title: 'Updated Title',
          is_active: false,
        };

        const mockResponse = createMockTimeBlock({ ...updates });
        vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate({ id: '123', updates });

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

        expect(apiClient.patch).toHaveBeenCalledWith('/time-blocks/123/', updates);
      });

      it('should parse string resource ID to integer', async () => {
        const updates = { resource: '789' };

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

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

        result.current.mutate({ id: '123', updates });

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

        expect(apiClient.patch).toHaveBeenCalledWith('/time-blocks/123/', {
          resource: 789,
        });
      });

      it('should handle null resource', async () => {
        const updates = { resource: null };

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

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

        result.current.mutate({ id: '123', updates });

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

        expect(apiClient.patch).toHaveBeenCalledWith('/time-blocks/123/', {
          resource: null,
        });
      });

      it('should invalidate queries on success', async () => {
        vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: {} });

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

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

        result.current.mutate({ id: '123', updates: { title: 'Updated' } });

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
      });
    });

    describe('useDeleteTimeBlock', () => {
      it('should delete a time block', async () => {
        vi.mocked(apiClient.delete).mockResolvedValueOnce({ data: {} });

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

        result.current.mutate('123');

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

        expect(apiClient.delete).toHaveBeenCalledWith('/time-blocks/123/');
      });

      it('should invalidate queries on success', async () => {
        vi.mocked(apiClient.delete).mockResolvedValueOnce({ data: {} });

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

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

        result.current.mutate('123');

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
      });

      it('should handle API errors', async () => {
        const error = new Error('Not Found');
        vi.mocked(apiClient.delete).mockRejectedValueOnce(error);

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

        result.current.mutate('123');

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

    describe('useToggleTimeBlock', () => {
      it('should toggle a time block active status', async () => {
        const mockResponse = { is_active: false };
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate('123');

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/123/toggle/');
        expect(result.current.data).toEqual(mockResponse);
      });

      it('should invalidate queries on success', async () => {
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: { is_active: true } });

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

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

        result.current.mutate('123');

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
      });
    });

    describe('useApproveTimeBlock', () => {
      it('should approve a time block without notes', async () => {
        const mockResponse = { approval_status: 'APPROVED' };
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate({ id: '123' });

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/123/approve/', {});
      });

      it('should approve a time block with notes', async () => {
        const mockResponse = { approval_status: 'APPROVED' };
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate({ id: '123', notes: 'Looks good' });

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/123/approve/', {
          notes: 'Looks good',
        });
      });

      it('should invalidate all relevant queries on success', async () => {
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: {} });

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

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

        result.current.mutate({ id: '123' });

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-block-pending-reviews'] });
      });
    });

    describe('useDenyTimeBlock', () => {
      it('should deny a time block without notes', async () => {
        const mockResponse = { approval_status: 'DENIED' };
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate({ id: '123' });

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/123/deny/', {});
      });

      it('should deny a time block with notes', async () => {
        const mockResponse = { approval_status: 'DENIED' };
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate({ id: '123', notes: 'Conflicts with existing events' });

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/123/deny/', {
          notes: 'Conflicts with existing events',
        });
      });

      it('should invalidate all relevant queries on success', async () => {
        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: {} });

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

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

        result.current.mutate({ id: '123' });

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

        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['blocked-dates'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['my-blocks'] });
        expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['time-block-pending-reviews'] });
      });
    });

    describe('useCheckConflicts', () => {
      it('should check for conflicts with no conflicts found', async () => {
        const checkData: CheckConflictsData = {
          recurrence_type: 'WEEKLY',
          recurrence_pattern: { days_of_week: [1, 3, 5] },
          all_day: false,
          start_time: '09:00:00',
          end_time: '17:00:00',
        };

        const mockResponse: TimeBlockConflictCheck = {
          has_conflicts: false,
          conflict_count: 0,
          conflicts: [],
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate(checkData);

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/check_conflicts/', {
          ...checkData,
          resource_id: null,
        });
        expect(result.current.data).toEqual(mockResponse);
      });

      it('should check for conflicts with conflicts found', async () => {
        const checkData: CheckConflictsData = {
          recurrence_type: 'NONE',
          start_date: '2025-01-15',
          end_date: '2025-01-15',
          resource_id: '456',
          all_day: true,
        };

        const mockResponse: TimeBlockConflictCheck = {
          has_conflicts: true,
          conflict_count: 2,
          conflicts: [
            {
              event_id: 'evt1',
              title: 'Meeting',
              start_time: '2025-01-15T10:00:00Z',
              end_time: '2025-01-15T11:00:00Z',
            },
            {
              event_id: 'evt2',
              title: 'Appointment',
              start_time: '2025-01-15T14:00:00Z',
              end_time: '2025-01-15T15:00:00Z',
            },
          ],
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({ data: mockResponse });

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

        result.current.mutate(checkData);

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

        expect(result.current.data?.has_conflicts).toBe(true);
        expect(result.current.data?.conflict_count).toBe(2);
        expect(result.current.data?.conflicts).toHaveLength(2);
      });

      it('should parse string resource_id to integer', async () => {
        const checkData: CheckConflictsData = {
          recurrence_type: 'NONE',
          resource_id: '789',
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({
          data: { has_conflicts: false, conflict_count: 0, conflicts: [] },
        });

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

        result.current.mutate(checkData);

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/check_conflicts/', {
          recurrence_type: 'NONE',
          resource_id: 789,
        });
      });

      it('should handle null resource_id', async () => {
        const checkData: CheckConflictsData = {
          recurrence_type: 'MONTHLY',
          resource_id: null,
        };

        vi.mocked(apiClient.post).mockResolvedValueOnce({
          data: { has_conflicts: false, conflict_count: 0, conflicts: [] },
        });

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

        result.current.mutate(checkData);

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

        expect(apiClient.post).toHaveBeenCalledWith('/time-blocks/check_conflicts/', {
          recurrence_type: 'MONTHLY',
          resource_id: null,
        });
      });
    });
  });

  describe('Holiday Hooks', () => {
    describe('useHolidays', () => {
      it('should fetch all holidays without country filter', async () => {
        const mockHolidays = [
          createMockHoliday({ code: 'new-year', name: 'New Year\'s Day' }),
          createMockHoliday({ code: 'christmas', name: 'Christmas Day', month: 12, day: 25 }),
        ];

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockHolidays });

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

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/?');
        expect(result.current.data).toEqual(mockHolidays);
      });

      it('should fetch holidays with country filter', async () => {
        const mockHolidays = [createMockHoliday({ country: 'CA' })];

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockHolidays });

        const { result } = renderHook(() => useHolidays('CA'), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/?country=CA');
      });
    });

    describe('useHoliday', () => {
      it('should fetch a single holiday by code', async () => {
        const mockHoliday = createMockHoliday({
          code: 'thanksgiving',
          name: 'Thanksgiving',
          holiday_type: 'CALCULATED',
        });

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockHoliday });

        const { result } = renderHook(() => useHoliday('thanksgiving'), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/thanksgiving/');
        expect(result.current.data).toEqual(mockHoliday);
      });

      it('should not fetch when code is empty', async () => {
        const { result } = renderHook(() => useHoliday(''), {
          wrapper: createWrapper(queryClient),
        });

        expect(result.current.status).toBe('pending');
        expect(apiClient.get).not.toHaveBeenCalled();
      });
    });

    describe('useHolidayDates', () => {
      it('should fetch holiday dates without filters', async () => {
        const mockResponse = {
          year: 2025,
          holidays: [
            { code: 'new-year', name: 'New Year\'s Day', date: '2025-01-01' },
            { code: 'christmas', name: 'Christmas Day', date: '2025-12-25' },
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

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

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/dates/?');
        expect(result.current.data).toEqual(mockResponse);
      });

      it('should fetch holiday dates with year filter', async () => {
        const mockResponse = {
          year: 2026,
          holidays: [
            { code: 'new-year', name: 'New Year\'s Day', date: '2026-01-01' },
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

        const { result } = renderHook(() => useHolidayDates(2026), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/dates/?year=2026');
      });

      it('should fetch holiday dates with year and country filters', async () => {
        const mockResponse = {
          year: 2025,
          holidays: [
            { code: 'victoria-day', name: 'Victoria Day', date: '2025-05-19' },
          ],
        };

        vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockResponse });

        const { result } = renderHook(() => useHolidayDates(2025, 'CA'), {
          wrapper: createWrapper(queryClient),
        });

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

        expect(apiClient.get).toHaveBeenCalledWith('/holidays/dates/?year=2025&country=CA');
      });
    });
  });
});
