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 the API client
vi.mock('../../api/client', () => ({
  default: {
    get: vi.fn(),
    post: vi.fn(),
    delete: vi.fn(),
  },
}));

import {
  useCustomerBilling,
  useCustomerPaymentMethods,
  useCreateSetupIntent,
  useDeletePaymentMethod,
  useSetDefaultPaymentMethod,
} from '../useCustomerBilling';
import apiClient from '../../api/client';

// Create wrapper with fresh QueryClient for each test
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('useCustomerBilling hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useCustomerBilling', () => {
    it('fetches customer billing data successfully', async () => {
      const mockBillingData = {
        outstanding: [
          {
            id: 1,
            title: 'Haircut Appointment',
            service_name: 'Basic Haircut',
            amount: 5000,
            amount_display: '$50.00',
            status: 'confirmed',
            start_time: '2025-12-08T10:00:00Z',
            end_time: '2025-12-08T10:30:00Z',
            payment_status: 'unpaid' as const,
            payment_intent_id: null,
          },
          {
            id: 2,
            title: 'Massage Session',
            service_name: 'Deep Tissue Massage',
            amount: 8000,
            amount_display: '$80.00',
            status: 'confirmed',
            start_time: '2025-12-09T14:00:00Z',
            end_time: '2025-12-09T15:00:00Z',
            payment_status: 'pending' as const,
            payment_intent_id: 'pi_123456',
          },
        ],
        payment_history: [
          {
            id: 1,
            event_id: 100,
            event_title: 'Haircut - John Doe',
            service_name: 'Premium Haircut',
            amount: 7500,
            amount_display: '$75.00',
            currency: 'usd',
            status: 'succeeded',
            payment_intent_id: 'pi_completed_123',
            created_at: '2025-12-01T10:00:00Z',
            completed_at: '2025-12-01T10:05:00Z',
            event_date: '2025-12-01T14:00:00Z',
          },
          {
            id: 2,
            event_id: 101,
            event_title: 'Spa Treatment',
            service_name: 'Facial Treatment',
            amount: 12000,
            amount_display: '$120.00',
            currency: 'usd',
            status: 'succeeded',
            payment_intent_id: 'pi_completed_456',
            created_at: '2025-11-28T09:00:00Z',
            completed_at: '2025-11-28T09:02:00Z',
            event_date: '2025-11-28T15:30:00Z',
          },
        ],
        summary: {
          total_spent: 19500,
          total_spent_display: '$195.00',
          total_outstanding: 13000,
          total_outstanding_display: '$130.00',
          payment_count: 2,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue({ data: mockBillingData } as any);

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/payments/customer/billing/');
      expect(apiClient.get).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockBillingData);
    });

    it('handles empty billing data', async () => {
      const mockEmptyData = {
        outstanding: [],
        payment_history: [],
        summary: {
          total_spent: 0,
          total_spent_display: '$0.00',
          total_outstanding: 0,
          total_outstanding_display: '$0.00',
          payment_count: 0,
        },
      };

      vi.mocked(apiClient.get).mockResolvedValue({ data: mockEmptyData } as any);

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

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

      expect(result.current.data?.outstanding).toHaveLength(0);
      expect(result.current.data?.payment_history).toHaveLength(0);
      expect(result.current.data?.summary.payment_count).toBe(0);
    });

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

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

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

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

    it('uses 30 second staleTime', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: { outstanding: [], payment_history: [], summary: {} } } as any);

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

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

      renderHook(() => useCustomerBilling(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['customerBilling']);
        expect(queryState).toBeDefined();
      });

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

    it('does not retry on failure', async () => {
      vi.mocked(apiClient.get).mockRejectedValue(new Error('Network error'));

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

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

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

  describe('useCustomerPaymentMethods', () => {
    it('fetches payment methods successfully', async () => {
      const mockPaymentMethods = {
        payment_methods: [
          {
            id: 'pm_123456',
            type: 'card',
            brand: 'visa',
            last4: '4242',
            exp_month: 12,
            exp_year: 2025,
            is_default: true,
          },
          {
            id: 'pm_789012',
            type: 'card',
            brand: 'mastercard',
            last4: '5555',
            exp_month: 6,
            exp_year: 2026,
            is_default: false,
          },
        ],
        has_stripe_customer: true,
      };

      vi.mocked(apiClient.get).mockResolvedValue({ data: mockPaymentMethods } as any);

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

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

      expect(apiClient.get).toHaveBeenCalledWith('/payments/customer/payment-methods/');
      expect(apiClient.get).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockPaymentMethods);
      expect(result.current.data?.payment_methods).toHaveLength(2);
    });

    it('handles no payment methods', async () => {
      const mockNoPaymentMethods = {
        payment_methods: [],
        has_stripe_customer: false,
        message: 'No payment methods found',
      };

      vi.mocked(apiClient.get).mockResolvedValue({ data: mockNoPaymentMethods } as any);

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

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

      expect(result.current.data?.payment_methods).toHaveLength(0);
      expect(result.current.data?.has_stripe_customer).toBe(false);
      expect(result.current.data?.message).toBe('No payment methods found');
    });

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

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

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

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

    it('uses 60 second staleTime', async () => {
      vi.mocked(apiClient.get).mockResolvedValue({ data: { payment_methods: [], has_stripe_customer: false } } as any);

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

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

      renderHook(() => useCustomerPaymentMethods(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['customerPaymentMethods']);
        expect(queryState).toBeDefined();
      });

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

    it('does not retry on failure', async () => {
      vi.mocked(apiClient.get).mockRejectedValue(new Error('Network error'));

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

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

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

  describe('useCreateSetupIntent', () => {
    it('creates setup intent successfully', async () => {
      const mockSetupIntent = {
        client_secret: 'seti_123_secret_456',
        setup_intent_id: 'seti_123456',
        customer_id: 'cus_789012',
        stripe_account: '',
        publishable_key: 'pk_test_123456',
      };

      vi.mocked(apiClient.post).mockResolvedValue({ data: mockSetupIntent } as any);

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

      await act(async () => {
        const response = await result.current.mutateAsync();
        expect(response).toEqual(mockSetupIntent);
      });

      expect(apiClient.post).toHaveBeenCalledWith('/payments/customer/setup-intent/');
      expect(apiClient.post).toHaveBeenCalledTimes(1);
    });

    it('creates setup intent with connected account', async () => {
      const mockSetupIntent = {
        client_secret: 'seti_123_secret_789',
        setup_intent_id: 'seti_789012',
        customer_id: 'cus_345678',
        stripe_account: 'acct_connect_123',
        publishable_key: undefined,
      };

      vi.mocked(apiClient.post).mockResolvedValue({ data: mockSetupIntent } as any);

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

      let response;
      await act(async () => {
        response = await result.current.mutateAsync();
      });

      expect(response).toEqual(mockSetupIntent);
      expect(response.stripe_account).toBe('acct_connect_123');
    });

    it('handles setup intent creation errors', async () => {
      const mockError = new Error('Failed to create setup intent');
      vi.mocked(apiClient.post).mockRejectedValue(mockError);

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

      let caughtError;
      await act(async () => {
        try {
          await result.current.mutateAsync();
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);
    });

    it('tracks mutation loading state', async () => {
      vi.mocked(apiClient.post).mockImplementation(
        () =>
          new Promise((resolve) =>
            setTimeout(() => resolve({ data: { client_secret: 'seti_test' } }), 50)
          )
      );

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

      expect(result.current.isPending).toBe(false);

      const promise = act(async () => {
        await result.current.mutateAsync();
      });

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

      await promise;
    });
  });

  describe('useDeletePaymentMethod', () => {
    it('deletes payment method successfully', async () => {
      const mockDeleteResponse = {
        success: true,
        message: 'Payment method deleted successfully',
      };

      vi.mocked(apiClient.delete).mockResolvedValue({ data: mockDeleteResponse } as any);

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

      await act(async () => {
        const response = await result.current.mutateAsync('pm_123456');
        expect(response).toEqual(mockDeleteResponse);
      });

      expect(apiClient.delete).toHaveBeenCalledWith('/payments/customer/payment-methods/pm_123456/');
      expect(apiClient.delete).toHaveBeenCalledTimes(1);
    });

    it('invalidates payment methods query on success', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({
        data: { success: true, message: 'Deleted' },
      } as any);

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

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

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

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

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

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['customerPaymentMethods'] });
    });

    it('handles delete errors gracefully', async () => {
      const mockError = new Error('Cannot delete default payment method');
      vi.mocked(apiClient.delete).mockRejectedValue(mockError);

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

      let caughtError;
      await act(async () => {
        try {
          await result.current.mutateAsync('pm_123456');
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);
    });

    it('does not invalidate queries on error', async () => {
      vi.mocked(apiClient.delete).mockRejectedValue(new Error('Delete failed'));

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

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

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

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

      await act(async () => {
        try {
          await result.current.mutateAsync('pm_123456');
        } catch {
          // Expected to fail
        }
      });

      expect(invalidateQueriesSpy).not.toHaveBeenCalled();
    });

    it('handles multiple payment method deletions', async () => {
      vi.mocked(apiClient.delete).mockResolvedValue({
        data: { success: true, message: 'Deleted' },
      } as any);

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

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

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

      expect(apiClient.delete).toHaveBeenCalledTimes(2);
      expect(apiClient.delete).toHaveBeenNthCalledWith(1, '/payments/customer/payment-methods/pm_111111/');
      expect(apiClient.delete).toHaveBeenNthCalledWith(2, '/payments/customer/payment-methods/pm_222222/');
    });
  });

  describe('useSetDefaultPaymentMethod', () => {
    it('sets default payment method successfully', async () => {
      const mockSetDefaultResponse = {
        success: true,
        message: 'Default payment method updated',
      };

      vi.mocked(apiClient.post).mockResolvedValue({ data: mockSetDefaultResponse } as any);

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

      await act(async () => {
        const response = await result.current.mutateAsync('pm_789012');
        expect(response).toEqual(mockSetDefaultResponse);
      });

      expect(apiClient.post).toHaveBeenCalledWith('/payments/customer/payment-methods/pm_789012/default/');
      expect(apiClient.post).toHaveBeenCalledTimes(1);
    });

    it('invalidates payment methods query on success', async () => {
      vi.mocked(apiClient.post).mockResolvedValue({
        data: { success: true, message: 'Updated' },
      } as any);

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

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

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

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

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

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['customerPaymentMethods'] });
    });

    it('handles set default errors gracefully', async () => {
      const mockError = new Error('Payment method not found');
      vi.mocked(apiClient.post).mockRejectedValue(mockError);

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

      let caughtError;
      await act(async () => {
        try {
          await result.current.mutateAsync('pm_invalid');
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);
    });

    it('does not invalidate queries on error', async () => {
      vi.mocked(apiClient.post).mockRejectedValue(new Error('Update failed'));

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

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

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

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

      await act(async () => {
        try {
          await result.current.mutateAsync('pm_123456');
        } catch {
          // Expected to fail
        }
      });

      expect(invalidateQueriesSpy).not.toHaveBeenCalled();
    });

    it('handles switching default between payment methods', async () => {
      vi.mocked(apiClient.post).mockResolvedValue({
        data: { success: true, message: 'Updated' },
      } as any);

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

      // Set first method as default
      await act(async () => {
        await result.current.mutateAsync('pm_111111');
      });

      // Switch to second method as default
      await act(async () => {
        await result.current.mutateAsync('pm_222222');
      });

      expect(apiClient.post).toHaveBeenCalledTimes(2);
      expect(apiClient.post).toHaveBeenNthCalledWith(1, '/payments/customer/payment-methods/pm_111111/default/');
      expect(apiClient.post).toHaveBeenNthCalledWith(2, '/payments/customer/payment-methods/pm_222222/default/');
    });

    it('tracks mutation loading state', async () => {
      vi.mocked(apiClient.post).mockImplementation(
        () =>
          new Promise((resolve) =>
            setTimeout(() => resolve({ data: { success: true, message: 'Updated' } }), 50)
          )
      );

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

      expect(result.current.isPending).toBe(false);

      const promise = act(async () => {
        await result.current.mutateAsync('pm_123456');
      });

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

      await promise;
    });
  });
});
