import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

// Mock the sandbox API
vi.mock('../../api/sandbox', () => ({
  getSandboxStatus: vi.fn(),
  toggleSandboxMode: vi.fn(),
  resetSandboxData: vi.fn(),
}));

import {
  useSandboxStatus,
  useToggleSandbox,
  useResetSandbox,
} from '../useSandbox';
import * as sandboxApi from '../../api/sandbox';

// Create wrapper
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return function Wrapper({ children }: { children: React.ReactNode }) {
    return React.createElement(QueryClientProvider, { client: queryClient }, children);
  };
};

describe('useSandbox hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useSandboxStatus', () => {
    it('fetches sandbox status', async () => {
      const mockStatus = {
        sandbox_mode: true,
        sandbox_enabled: true,
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus);

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

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

      expect(sandboxApi.getSandboxStatus).toHaveBeenCalled();
      expect(result.current.data).toEqual(mockStatus);
    });

    it('returns sandbox_mode as false when in live mode', async () => {
      const mockStatus = {
        sandbox_mode: false,
        sandbox_enabled: true,
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus);

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

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

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

    it('handles sandbox not being enabled for business', async () => {
      const mockStatus = {
        sandbox_mode: false,
        sandbox_enabled: false,
        sandbox_schema: null,
      };
      vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus);

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

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

      expect(result.current.data?.sandbox_enabled).toBe(false);
      expect(result.current.data?.sandbox_schema).toBeNull();
    });

    it('handles API errors', async () => {
      vi.mocked(sandboxApi.getSandboxStatus).mockRejectedValue(
        new Error('Failed to fetch sandbox status')
      );

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

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

      expect(result.current.error).toBeInstanceOf(Error);
      expect(result.current.error?.message).toBe('Failed to fetch sandbox status');
    });

    it('configures staleTime to 30 seconds', async () => {
      const mockStatus = {
        sandbox_mode: false,
        sandbox_enabled: true,
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.getSandboxStatus).mockResolvedValue(mockStatus);

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

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

      // Data should be considered fresh for 30 seconds
      // This is configured in the hook with staleTime: 30 * 1000
      expect(result.current.isStale).toBe(false);
    });
  });

  describe('useToggleSandbox', () => {
    let originalLocation: Location;
    let reloadMock: ReturnType<typeof vi.fn>;

    beforeEach(() => {
      // Mock window.location.reload
      originalLocation = window.location;
      reloadMock = vi.fn();

      Object.defineProperty(window, 'location', {
        value: { ...originalLocation, reload: reloadMock },
        writable: true,
      });
    });

    afterEach(() => {
      // Restore window.location
      Object.defineProperty(window, 'location', {
        value: originalLocation,
        writable: true,
      });
    });

    it('toggles sandbox mode to enabled', async () => {
      const mockResponse = {
        sandbox_mode: true,
        message: 'Sandbox mode enabled',
      };
      vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse);

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

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

      expect(sandboxApi.toggleSandboxMode).toHaveBeenCalled();
      expect(vi.mocked(sandboxApi.toggleSandboxMode).mock.calls[0][0]).toBe(true);
    });

    it('toggles sandbox mode to disabled', async () => {
      const mockResponse = {
        sandbox_mode: false,
        message: 'Sandbox mode disabled',
      };
      vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse);

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

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

      expect(sandboxApi.toggleSandboxMode).toHaveBeenCalled();
      expect(vi.mocked(sandboxApi.toggleSandboxMode).mock.calls[0][0]).toBe(false);
    });

    it('updates sandbox status in cache on success', async () => {
      const mockResponse = {
        sandbox_mode: true,
        message: 'Sandbox mode enabled',
      };
      vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse);

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

      // Pre-populate cache with initial status
      queryClient.setQueryData(['sandboxStatus'], {
        sandbox_mode: false,
        sandbox_enabled: true,
        sandbox_schema: 'business_1_sandbox',
      });

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

      const { result } = renderHook(() => useToggleSandbox(), {
        wrapper: CustomWrapper,
      });

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

      // Verify cache was updated
      const cachedData = queryClient.getQueryData(['sandboxStatus']);
      expect(cachedData).toEqual({
        sandbox_mode: true,
        sandbox_enabled: true,
        sandbox_schema: 'business_1_sandbox',
      });
    });

    it('reloads window after successful toggle', async () => {
      const mockResponse = {
        sandbox_mode: true,
        message: 'Sandbox mode enabled',
      };
      vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse);

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

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

      expect(reloadMock).toHaveBeenCalled();
    });

    it('handles toggle errors without reloading', async () => {
      vi.mocked(sandboxApi.toggleSandboxMode).mockRejectedValue(
        new Error('Failed to toggle sandbox mode')
      );

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

      await act(async () => {
        try {
          await result.current.mutateAsync(true);
        } catch (error) {
          // Expected to throw
        }
      });

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

    it('updates cache even when old data is undefined', async () => {
      const mockResponse = {
        sandbox_mode: true,
        message: 'Sandbox mode enabled',
      };
      vi.mocked(sandboxApi.toggleSandboxMode).mockResolvedValue(mockResponse);

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

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

      const { result } = renderHook(() => useToggleSandbox(), {
        wrapper: CustomWrapper,
      });

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

      // Verify cache was set even with no prior data
      const cachedData = queryClient.getQueryData(['sandboxStatus']);
      expect(cachedData).toMatchObject({
        sandbox_mode: true,
      });
    });
  });

  describe('useResetSandbox', () => {
    it('resets sandbox data', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

      expect(sandboxApi.resetSandboxData).toHaveBeenCalled();
    });

    it('invalidates resource queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

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

    it('invalidates event queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

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

    it('invalidates service queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

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

    it('invalidates customer queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

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

    it('invalidates payment queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

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

    it('invalidates all required queries on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

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

      // Verify all expected queries were invalidated
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['resources'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['events'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['services'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['customers'] });
      expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['payments'] });
      expect(invalidateSpy).toHaveBeenCalledTimes(5);
    });

    it('handles reset errors', async () => {
      vi.mocked(sandboxApi.resetSandboxData).mockRejectedValue(
        new Error('Failed to reset sandbox data')
      );

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

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

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

      const { result } = renderHook(() => useResetSandbox(), {
        wrapper: CustomWrapper,
      });

      await act(async () => {
        try {
          await result.current.mutateAsync();
        } catch (error) {
          // Expected to throw
        }
      });

      // Verify queries were NOT invalidated on error
      expect(invalidateSpy).not.toHaveBeenCalled();
    });

    it('returns response data on success', async () => {
      const mockResponse = {
        message: 'Sandbox data reset successfully',
        sandbox_schema: 'business_1_sandbox',
      };
      vi.mocked(sandboxApi.resetSandboxData).mockResolvedValue(mockResponse);

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

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

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