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 from 'react';
import apiClient from '../../api/client';
import {
  useCommunicationCredits,
  useCreditTransactions,
  useUpdateCreditsSettings,
  useAddCredits,
  useCreatePaymentIntent,
  useConfirmPayment,
  useSetupPaymentMethod,
  useSavePaymentMethod,
  useCommunicationUsageStats,
  usePhoneNumbers,
  useSearchPhoneNumbers,
  usePurchasePhoneNumber,
  useReleasePhoneNumber,
  useChangePhoneNumber,
  CommunicationCredits,
  CreditTransaction,
  ProxyPhoneNumber,
  AvailablePhoneNumber,
} from '../useCommunicationCredits';

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

describe('useCommunicationCredits', () => {
  let queryClient: QueryClient;
  let wrapper: React.FC<{ children: React.ReactNode }>;

  const mockCredits: CommunicationCredits = {
    id: 1,
    balance_cents: 50000,
    auto_reload_enabled: true,
    auto_reload_threshold_cents: 10000,
    auto_reload_amount_cents: 50000,
    low_balance_warning_cents: 20000,
    low_balance_warning_sent: false,
    stripe_payment_method_id: 'pm_test123',
    last_twilio_sync_at: '2025-12-07T10:00:00Z',
    total_loaded_cents: 100000,
    total_spent_cents: 50000,
    created_at: '2025-01-01T00:00:00Z',
    updated_at: '2025-12-07T10:00:00Z',
  };

  const mockTransactions: CreditTransaction[] = [
    {
      id: 1,
      amount_cents: 50000,
      balance_after_cents: 50000,
      transaction_type: 'manual',
      description: 'Manual credit purchase',
      reference_type: 'payment_intent',
      reference_id: 'pi_test123',
      stripe_charge_id: 'ch_test123',
      created_at: '2025-12-07T10:00:00Z',
    },
    {
      id: 2,
      amount_cents: -1000,
      balance_after_cents: 49000,
      transaction_type: 'usage',
      description: 'SMS to +15551234567',
      reference_type: 'sms_message',
      reference_id: 'msg_123',
      stripe_charge_id: '',
      created_at: '2025-12-07T11:00:00Z',
    },
  ];

  const mockPhoneNumber: ProxyPhoneNumber = {
    id: 1,
    phone_number: '+15551234567',
    friendly_name: 'Main Office Line',
    status: 'assigned',
    monthly_fee_cents: 100,
    capabilities: {
      voice: true,
      sms: true,
      mms: true,
    },
    assigned_at: '2025-12-01T00:00:00Z',
    last_billed_at: '2025-12-01T00:00:00Z',
  };

  const mockAvailableNumber: AvailablePhoneNumber = {
    phone_number: '+15559876543',
    friendly_name: '(555) 987-6543',
    locality: 'New York',
    region: 'NY',
    postal_code: '10001',
    capabilities: {
      voice: true,
      sms: true,
      mms: true,
    },
    monthly_cost_cents: 100,
  };

  beforeEach(() => {
    queryClient = new QueryClient({
      defaultOptions: {
        queries: { retry: false },
        mutations: { retry: false },
      },
    });

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

    vi.clearAllMocks();
  });

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

  describe('useCommunicationCredits', () => {
    it('should fetch communication credits successfully', async () => {
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits });

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/');
      expect(result.current.data).toEqual(mockCredits);
      expect(result.current.isLoading).toBe(false);
      expect(result.current.error).toBeNull();
    });

    it('should handle fetch errors', async () => {
      const mockError = new Error('Failed to fetch credits');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

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

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

      expect(result.current.data).toBeUndefined();
      expect(result.current.error).toEqual(mockError);
    });

    it('should use correct query key', async () => {
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits });

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

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

      const cachedData = queryClient.getQueryData(['communicationCredits']);
      expect(cachedData).toEqual(mockCredits);
    });

    it('should have staleTime of 30 seconds', async () => {
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: mockCredits });

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

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

      const queryState = queryClient.getQueryState(['communicationCredits']);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useCreditTransactions', () => {
    it('should fetch credit transactions with pagination', async () => {
      const mockResponse = {
        results: mockTransactions,
        count: 2,
        next: null,
        previous: null,
      };

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

      const { result } = renderHook(() => useCreditTransactions(1, 20), { wrapper });

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/transactions/', {
        params: { page: 1, limit: 20 },
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should support custom page and limit', async () => {
      const mockResponse = {
        results: [mockTransactions[0]],
        count: 10,
        next: 'http://api.example.com/page=3',
        previous: 'http://api.example.com/page=1',
      };

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

      const { result } = renderHook(() => useCreditTransactions(2, 10), { wrapper });

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/transactions/', {
        params: { page: 2, limit: 10 },
      });
    });

    it('should handle fetch errors', async () => {
      const mockError = new Error('Failed to fetch transactions');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

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

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useUpdateCreditsSettings', () => {
    it('should update credit settings successfully', async () => {
      const updatedCredits = {
        ...mockCredits,
        auto_reload_enabled: false,
        auto_reload_threshold_cents: 5000,
      };

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

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

      result.current.mutate({
        auto_reload_enabled: false,
        auto_reload_threshold_cents: 5000,
      });

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

      expect(apiClient.patch).toHaveBeenCalledWith('/communication-credits/settings/', {
        auto_reload_enabled: false,
        auto_reload_threshold_cents: 5000,
      });
      expect(result.current.data).toEqual(updatedCredits);
    });

    it('should update query cache on success', async () => {
      const updatedCredits = { ...mockCredits, auto_reload_enabled: false };

      queryClient.setQueryData(['communicationCredits'], mockCredits);
      vi.mocked(apiClient.patch).mockResolvedValueOnce({ data: updatedCredits });

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

      result.current.mutate({ auto_reload_enabled: false });

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

      const cachedData = queryClient.getQueryData(['communicationCredits']);
      expect(cachedData).toEqual(updatedCredits);
    });

    it('should handle update errors', async () => {
      const mockError = new Error('Failed to update settings');
      vi.mocked(apiClient.patch).mockRejectedValueOnce(mockError);

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

      result.current.mutate({ auto_reload_enabled: false });

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useAddCredits', () => {
    it('should add credits successfully', async () => {
      const mockResponse = {
        success: true,
        balance_cents: 100000,
        transaction_id: 123,
      };

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

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

      result.current.mutate({
        amount_cents: 50000,
        payment_method_id: 'pm_test123',
      });

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/add/', {
        amount_cents: 50000,
        payment_method_id: 'pm_test123',
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate credits and transactions queries on success', async () => {
      const mockResponse = { success: true, balance_cents: 100000 };

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

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

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

      result.current.mutate({ amount_cents: 50000 });

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] });
    });

    it('should handle add credits errors', async () => {
      const mockError = new Error('Payment failed');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate({ amount_cents: 50000 });

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useCreatePaymentIntent', () => {
    it('should create payment intent successfully', async () => {
      const mockResponse = {
        client_secret: 'pi_test_secret',
        payment_intent_id: 'pi_test123',
      };

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

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

      result.current.mutate(50000);

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/create-payment-intent/', {
        amount_cents: 50000,
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should handle payment intent creation errors', async () => {
      const mockError = new Error('Failed to create payment intent');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate(50000);

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useConfirmPayment', () => {
    it('should confirm payment successfully', async () => {
      const mockResponse = {
        success: true,
        balance_cents: 100000,
        transaction_id: 123,
      };

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

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

      result.current.mutate({
        payment_intent_id: 'pi_test123',
        save_payment_method: true,
      });

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/confirm-payment/', {
        payment_intent_id: 'pi_test123',
        save_payment_method: true,
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate credits and transactions queries on success', async () => {
      const mockResponse = { success: true };

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

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

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

      result.current.mutate({ payment_intent_id: 'pi_test123' });

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] });
    });

    it('should handle confirmation errors', async () => {
      const mockError = new Error('Payment confirmation failed');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate({ payment_intent_id: 'pi_test123' });

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useSetupPaymentMethod', () => {
    it('should setup payment method successfully', async () => {
      const mockResponse = {
        client_secret: 'seti_test_secret',
      };

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

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

      result.current.mutate();

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/setup-payment-method/');
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should handle setup errors', async () => {
      const mockError = new Error('Failed to setup payment method');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate();

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useSavePaymentMethod', () => {
    it('should save payment method successfully', async () => {
      const mockResponse = {
        success: true,
        payment_method_id: 'pm_test123',
      };

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

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

      result.current.mutate('pm_test123');

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/save-payment-method/', {
        payment_method_id: 'pm_test123',
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate credits query on success', async () => {
      const mockResponse = { success: true };

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

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

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

      result.current.mutate('pm_test123');

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

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

    it('should handle save errors', async () => {
      const mockError = new Error('Failed to save payment method');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate('pm_test123');

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useCommunicationUsageStats', () => {
    it('should fetch usage stats successfully', async () => {
      const mockStats = {
        sms_sent_this_month: 150,
        voice_minutes_this_month: 45.5,
        proxy_numbers_active: 2,
        estimated_cost_cents: 2500,
      };

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

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/usage-stats/');
      expect(result.current.data).toEqual(mockStats);
    });

    it('should handle fetch errors', async () => {
      const mockError = new Error('Failed to fetch stats');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

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

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

      expect(result.current.error).toEqual(mockError);
    });

    it('should use correct query key', async () => {
      const mockStats = {
        sms_sent_this_month: 150,
        voice_minutes_this_month: 45.5,
        proxy_numbers_active: 2,
        estimated_cost_cents: 2500,
      };

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

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

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

      const cachedData = queryClient.getQueryData(['communicationUsageStats']);
      expect(cachedData).toEqual(mockStats);
    });
  });

  describe('usePhoneNumbers', () => {
    it('should fetch phone numbers successfully', async () => {
      const mockResponse = {
        numbers: [mockPhoneNumber],
        count: 1,
      };

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

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/');
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should handle fetch errors', async () => {
      const mockError = new Error('Failed to fetch phone numbers');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

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

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useSearchPhoneNumbers', () => {
    it('should search phone numbers successfully', async () => {
      const mockResponse = {
        numbers: [mockAvailableNumber],
        count: 1,
      };

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

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

      result.current.mutate({
        area_code: '555',
        country: 'US',
        limit: 10,
      });

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/search/', {
        params: {
          area_code: '555',
          country: 'US',
          limit: 10,
        },
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should support contains parameter', async () => {
      const mockResponse = {
        numbers: [mockAvailableNumber],
        count: 1,
      };

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

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

      result.current.mutate({
        contains: '123',
        country: 'US',
      });

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

      expect(apiClient.get).toHaveBeenCalledWith('/communication-credits/phone-numbers/search/', {
        params: {
          contains: '123',
          country: 'US',
        },
      });
    });

    it('should handle search errors', async () => {
      const mockError = new Error('Search failed');
      vi.mocked(apiClient.get).mockRejectedValueOnce(mockError);

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

      result.current.mutate({ area_code: '555' });

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('usePurchasePhoneNumber', () => {
    it('should purchase phone number successfully', async () => {
      const mockResponse = {
        success: true,
        phone_number: mockPhoneNumber,
        balance_cents: 49900,
      };

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

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

      result.current.mutate({
        phone_number: '+15551234567',
        friendly_name: 'Main Office Line',
      });

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/purchase/', {
        phone_number: '+15551234567',
        friendly_name: 'Main Office Line',
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate queries on success', async () => {
      const mockResponse = {
        success: true,
        phone_number: mockPhoneNumber,
        balance_cents: 49900,
      };

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

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

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

      result.current.mutate({ phone_number: '+15551234567' });

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] });
    });

    it('should handle purchase errors', async () => {
      const mockError = new Error('Insufficient credits');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate({ phone_number: '+15551234567' });

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useReleasePhoneNumber', () => {
    it('should release phone number successfully', async () => {
      const mockResponse = {
        success: true,
        message: 'Phone number released successfully',
      };

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

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

      result.current.mutate(1);

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

      expect(apiClient.delete).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/');
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate queries on success', async () => {
      const mockResponse = {
        success: true,
        message: 'Phone number released successfully',
      };

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

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

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

      result.current.mutate(1);

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationUsageStats'] });
    });

    it('should handle release errors', async () => {
      const mockError = new Error('Failed to release phone number');
      vi.mocked(apiClient.delete).mockRejectedValueOnce(mockError);

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

      result.current.mutate(1);

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

      expect(result.current.error).toEqual(mockError);
    });
  });

  describe('useChangePhoneNumber', () => {
    it('should change phone number successfully', async () => {
      const newPhoneNumber = {
        ...mockPhoneNumber,
        phone_number: '+15559876543',
        friendly_name: 'Updated Office Line',
      };

      const mockResponse = {
        success: true,
        phone_number: newPhoneNumber,
        balance_cents: 49900,
      };

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

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

      result.current.mutate({
        numberId: 1,
        new_phone_number: '+15559876543',
        friendly_name: 'Updated Office Line',
      });

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

      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/change/', {
        new_phone_number: '+15559876543',
        friendly_name: 'Updated Office Line',
      });
      expect(result.current.data).toEqual(mockResponse);
    });

    it('should invalidate queries on success', async () => {
      const mockResponse = {
        success: true,
        phone_number: mockPhoneNumber,
        balance_cents: 49900,
      };

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

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

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

      result.current.mutate({
        numberId: 1,
        new_phone_number: '+15559876543',
      });

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

      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['phoneNumbers'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['communicationCredits'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['creditTransactions'] });
    });

    it('should handle change errors', async () => {
      const mockError = new Error('Failed to change phone number');
      vi.mocked(apiClient.post).mockRejectedValueOnce(mockError);

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

      result.current.mutate({
        numberId: 1,
        new_phone_number: '+15559876543',
      });

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

      expect(result.current.error).toEqual(mockError);
    });

    it('should exclude numberId from request body', async () => {
      const mockResponse = {
        success: true,
        phone_number: mockPhoneNumber,
        balance_cents: 49900,
      };

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

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

      result.current.mutate({
        numberId: 1,
        new_phone_number: '+15559876543',
      });

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

      // Verify numberId is NOT in the request body
      expect(apiClient.post).toHaveBeenCalledWith('/communication-credits/phone-numbers/1/change/', {
        new_phone_number: '+15559876543',
      });
    });
  });

  describe('Integration Tests', () => {
    it('should update credits after adding credits', async () => {
      const initialCredits = mockCredits;
      const updatedCredits = { ...mockCredits, balance_cents: 100000 };

      // Initial fetch
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: initialCredits });

      const { result: creditsResult } = renderHook(() => useCommunicationCredits(), { wrapper });

      await waitFor(() => expect(creditsResult.current.isSuccess).toBe(true));
      expect(creditsResult.current.data?.balance_cents).toBe(50000);

      // Add credits
      vi.mocked(apiClient.post).mockResolvedValueOnce({ data: { success: true } });
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: updatedCredits });

      const { result: addResult } = renderHook(() => useAddCredits(), { wrapper });

      addResult.current.mutate({ amount_cents: 50000 });

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

      // Refetch credits
      await creditsResult.current.refetch();

      expect(creditsResult.current.data?.balance_cents).toBe(100000);
    });

    it('should update phone numbers list after purchasing', async () => {
      const initialResponse = { numbers: [], count: 0 };
      const updatedResponse = { numbers: [mockPhoneNumber], count: 1 };

      // Initial fetch
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: initialResponse });

      const { result: numbersResult } = renderHook(() => usePhoneNumbers(), { wrapper });

      await waitFor(() => expect(numbersResult.current.isSuccess).toBe(true));
      expect(numbersResult.current.data?.count).toBe(0);

      // Purchase number
      vi.mocked(apiClient.post).mockResolvedValueOnce({
        data: {
          success: true,
          phone_number: mockPhoneNumber,
          balance_cents: 49900,
        },
      });
      vi.mocked(apiClient.get).mockResolvedValueOnce({ data: updatedResponse });

      const { result: purchaseResult } = renderHook(() => usePurchasePhoneNumber(), { wrapper });

      purchaseResult.current.mutate({ phone_number: '+15551234567' });

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

      // Refetch numbers
      await numbersResult.current.refetch();

      expect(numbersResult.current.data?.count).toBe(1);
      expect(numbersResult.current.data?.numbers[0]).toEqual(mockPhoneNumber);
    });
  });
});
