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 business API
vi.mock('../../api/business', () => ({
  getBusinessOAuthCredentials: vi.fn(),
  updateBusinessOAuthCredentials: vi.fn(),
}));

import {
  useBusinessOAuthCredentials,
  useUpdateBusinessOAuthCredentials,
} from '../useBusinessOAuthCredentials';
import * as businessApi from '../../api/business';

// Create wrapper for React Query
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('useBusinessOAuthCredentials hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useBusinessOAuthCredentials', () => {
    it('fetches business OAuth credentials successfully', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-client-id-123',
            client_secret: 'google-client-secret-456',
            has_secret: true,
          },
          microsoft: {
            client_id: 'microsoft-client-id-789',
            client_secret: 'microsoft-client-secret-012',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      // Initially loading
      expect(result.current.isLoading).toBe(true);

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

      expect(businessApi.getBusinessOAuthCredentials).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockResponse);
      expect(result.current.data?.credentials.google.client_id).toBe('google-client-id-123');
      expect(result.current.data?.credentials.google.has_secret).toBe(true);
      expect(result.current.data?.credentials.microsoft.client_id).toBe('microsoft-client-id-789');
      expect(result.current.data?.useCustomCredentials).toBe(true);
    });

    it('handles empty credentials', async () => {
      const mockResponse = {
        credentials: {},
        useCustomCredentials: false,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

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

      expect(result.current.data?.credentials).toEqual({});
      expect(result.current.data?.useCustomCredentials).toBe(false);
    });

    it('handles credentials with has_secret false', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-client-id-123',
            client_secret: '',
            has_secret: false,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

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

      expect(result.current.data?.credentials.google.has_secret).toBe(false);
      expect(result.current.data?.credentials.google.client_secret).toBe('');
    });

    it('handles multiple providers with mixed credential states', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-client-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
          microsoft: {
            client_id: 'microsoft-client-id',
            client_secret: '',
            has_secret: false,
          },
          github: {
            client_id: '',
            client_secret: '',
            has_secret: false,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

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

      expect(Object.keys(result.current.data?.credentials || {})).toHaveLength(3);
      expect(result.current.data?.credentials.google.has_secret).toBe(true);
      expect(result.current.data?.credentials.microsoft.has_secret).toBe(false);
      expect(result.current.data?.credentials.github.has_secret).toBe(false);
    });

    it('handles API error gracefully', async () => {
      const mockError = new Error('Failed to fetch OAuth credentials');
      vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);

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

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

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

    it('does not retry on failure (404)', async () => {
      vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(
        new Error('404 Not Found')
      );

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

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

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

    it('caches data with 5 minute stale time', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-client-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

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

      // Rerender should use cached data (within stale time)
      rerender();

      // Should still only be called once
      expect(businessApi.getBusinessOAuthCredentials).toHaveBeenCalledTimes(1);
    });

    it('handles 401 unauthorized error', async () => {
      const mockError = new Error('401 Unauthorized');
      vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);

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

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

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

    it('handles network error', async () => {
      const mockError = new Error('Network Error');
      vi.mocked(businessApi.getBusinessOAuthCredentials).mockRejectedValue(mockError);

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

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

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

  describe('useUpdateBusinessOAuthCredentials', () => {
    it('updates credentials for a single provider successfully', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'new-google-client-id',
            client_secret: 'new-google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'new-google-client-id',
              client_secret: 'new-google-secret',
            },
          },
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_id: 'new-google-client-id',
            client_secret: 'new-google-secret',
          },
        },
      });

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

      expect(result.current.data?.credentials.google.client_id).toBe('new-google-client-id');
      expect(result.current.data?.credentials.google.has_secret).toBe(true);
    });

    it('updates credentials for multiple providers', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
          microsoft: {
            client_id: 'microsoft-id',
            client_secret: 'microsoft-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'google-id',
              client_secret: 'google-secret',
            },
            microsoft: {
              client_id: 'microsoft-id',
              client_secret: 'microsoft-secret',
            },
          },
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
          },
          microsoft: {
            client_id: 'microsoft-id',
            client_secret: 'microsoft-secret',
          },
        },
      });

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

      expect(Object.keys(result.current.data?.credentials || {})).toHaveLength(2);
    });

    it('updates only client_id without client_secret', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'updated-google-id',
            client_secret: 'existing-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'updated-google-id',
            },
          },
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_id: 'updated-google-id',
          },
        },
      });

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

    it('updates only client_secret without client_id', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'existing-google-id',
            client_secret: 'new-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_secret: 'new-secret',
            },
          },
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_secret: 'new-secret',
          },
        },
      });

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

    it('updates useCustomCredentials flag only', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: false,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          useCustomCredentials: false,
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        useCustomCredentials: false,
      });

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

      expect(result.current.data?.useCustomCredentials).toBe(false);
    });

    it('updates both credentials and useCustomCredentials flag', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'custom-google-id',
            client_secret: 'custom-google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'custom-google-id',
              client_secret: 'custom-google-secret',
            },
          },
          useCustomCredentials: true,
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_id: 'custom-google-id',
            client_secret: 'custom-google-secret',
          },
        },
        useCustomCredentials: true,
      });

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

      expect(result.current.data?.useCustomCredentials).toBe(true);
      expect(result.current.data?.credentials.google.has_secret).toBe(true);
    });

    it('updates query cache on success', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

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

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'google-id',
              client_secret: 'google-secret',
            },
          },
        });
      });

      // Verify cache was updated
      const cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
      expect(cachedData).toEqual(mockResponse);
    });

    it('handles update error gracefully', async () => {
      const mockError = new Error('Failed to update credentials');
      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);

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

      let caughtError: any = null;

      await act(async () => {
        try {
          await result.current.mutateAsync({
            credentials: {
              google: {
                client_id: 'test-id',
              },
            },
          });
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);

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

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

    it('handles validation error from API', async () => {
      const mockError = new Error('Invalid client_id format');
      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);

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

      let caughtError: any = null;

      await act(async () => {
        try {
          await result.current.mutateAsync({
            credentials: {
              google: {
                client_id: 'invalid-format',
              },
            },
          });
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);

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

    it('handles clearing credentials by passing empty values', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: '',
            client_secret: '',
            has_secret: false,
          },
        },
        useCustomCredentials: false,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: '',
              client_secret: '',
            },
          },
          useCustomCredentials: false,
        });
      });

      expect(businessApi.updateBusinessOAuthCredentials).toHaveBeenCalledWith({
        credentials: {
          google: {
            client_id: '',
            client_secret: '',
          },
        },
        useCustomCredentials: false,
      });

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

      expect(result.current.data?.credentials.google.has_secret).toBe(false);
      expect(result.current.data?.useCustomCredentials).toBe(false);
    });

    it('handles permission error (403)', async () => {
      const mockError = new Error('403 Forbidden - Insufficient permissions');
      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockRejectedValue(mockError);

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

      let caughtError: any = null;

      await act(async () => {
        try {
          await result.current.mutateAsync({
            useCustomCredentials: true,
          });
        } catch (error) {
          caughtError = error;
        }
      });

      expect(caughtError).toEqual(mockError);

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

    it('preserves backend response structure with has_secret flags', async () => {
      const mockResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
          microsoft: {
            client_id: 'microsoft-id',
            client_secret: '',
            has_secret: false,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(mockResponse);

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

      await act(async () => {
        await result.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'google-id',
              client_secret: 'google-secret',
            },
            microsoft: {
              client_id: 'microsoft-id',
            },
          },
        });
      });

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

      expect(result.current.data?.credentials.google.has_secret).toBe(true);
      expect(result.current.data?.credentials.microsoft.has_secret).toBe(false);
      expect(result.current.data?.credentials.microsoft.client_secret).toBe('');
    });
  });

  describe('integration tests', () => {
    it('fetches credentials then updates them', async () => {
      const initialResponse = {
        credentials: {
          google: {
            client_id: 'initial-google-id',
            client_secret: 'initial-google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      const updatedResponse = {
        credentials: {
          google: {
            client_id: 'updated-google-id',
            client_secret: 'updated-google-secret',
            has_secret: true,
          },
          microsoft: {
            client_id: 'new-microsoft-id',
            client_secret: 'new-microsoft-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(initialResponse);
      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(updatedResponse);

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

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

      // Fetch initial credentials
      const { result: fetchResult } = renderHook(() => useBusinessOAuthCredentials(), {
        wrapper,
      });

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

      expect(fetchResult.current.data?.credentials.google.client_id).toBe('initial-google-id');
      expect(Object.keys(fetchResult.current.data?.credentials || {})).toHaveLength(1);

      // Update credentials
      const { result: updateResult } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
        wrapper,
      });

      await act(async () => {
        await updateResult.current.mutateAsync({
          credentials: {
            google: {
              client_id: 'updated-google-id',
              client_secret: 'updated-google-secret',
            },
            microsoft: {
              client_id: 'new-microsoft-id',
              client_secret: 'new-microsoft-secret',
            },
          },
        });
      });

      // Verify cache was updated
      const cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
      expect(cachedData).toEqual(updatedResponse);
      expect((cachedData as any).credentials.google.client_id).toBe('updated-google-id');
      expect((cachedData as any).credentials.microsoft.client_id).toBe('new-microsoft-id');
    });

    it('toggles custom credentials on and off', async () => {
      const initialResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      const toggledOffResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: false,
      };

      const toggledOnResponse = {
        credentials: {
          google: {
            client_id: 'google-id',
            client_secret: 'google-secret',
            has_secret: true,
          },
        },
        useCustomCredentials: true,
      };

      vi.mocked(businessApi.getBusinessOAuthCredentials).mockResolvedValue(initialResponse);

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

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

      // Fetch initial state
      const { result: fetchResult } = renderHook(() => useBusinessOAuthCredentials(), {
        wrapper,
      });

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

      expect(fetchResult.current.data?.useCustomCredentials).toBe(true);

      // Toggle off
      const { result: updateResult } = renderHook(() => useUpdateBusinessOAuthCredentials(), {
        wrapper,
      });

      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(toggledOffResponse);

      await act(async () => {
        await updateResult.current.mutateAsync({
          useCustomCredentials: false,
        });
      });

      let cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
      expect((cachedData as any).useCustomCredentials).toBe(false);

      // Toggle back on
      vi.mocked(businessApi.updateBusinessOAuthCredentials).mockResolvedValue(toggledOnResponse);

      await act(async () => {
        await updateResult.current.mutateAsync({
          useCustomCredentials: true,
        });
      });

      cachedData = queryClient.getQueryData(['businessOAuthCredentials']);
      expect((cachedData as any).useCustomCredentials).toBe(true);
    });
  });
});
