/**
 * Unit tests for SandboxContext
 *
 * Tests the sandbox context provider and hook including:
 * - Default values when used outside provider
 * - Providing sandbox status from hooks
 * - Toggle functionality
 * - Loading and pending states
 * - localStorage synchronization
 */

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

// Mock the sandbox hooks
vi.mock('../../hooks/useSandbox', () => ({
  useSandboxStatus: vi.fn(),
  useToggleSandbox: vi.fn(),
}));

import { SandboxProvider, useSandbox } from '../SandboxContext';
import { useSandboxStatus, useToggleSandbox } from '../../hooks/useSandbox';

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};

  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => {
      store[key] = value.toString();
    },
    removeItem: (key: string) => {
      delete store[key];
    },
    clear: () => {
      store = {};
    },
  };
})();

Object.defineProperty(window, 'localStorage', {
  value: localStorageMock,
});

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

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

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

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

  describe('useSandbox hook', () => {
    it('should return default values when used outside provider', () => {
      const { result } = renderHook(() => useSandbox());

      expect(result.current).toEqual({
        isSandbox: false,
        sandboxEnabled: false,
        isLoading: false,
        toggleSandbox: expect.any(Function),
        isToggling: false,
      });
    });

    it('should allow calling toggleSandbox without error when outside provider', async () => {
      const { result } = renderHook(() => useSandbox());

      // Should not throw an error
      await expect(result.current.toggleSandbox(true)).resolves.toBeUndefined();
    });
  });

  describe('SandboxProvider', () => {
    describe('sandbox status', () => {
      it('should provide sandbox status from hook when sandbox is disabled', async () => {
        const mockStatusData = {
          sandbox_mode: false,
          sandbox_enabled: false,
        };

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: mockStatusData,
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isSandbox).toBe(false);
        expect(result.current.sandboxEnabled).toBe(false);
        expect(result.current.isLoading).toBe(false);
      });

      it('should provide sandbox status when sandbox is enabled and active', async () => {
        const mockStatusData = {
          sandbox_mode: true,
          sandbox_enabled: true,
        };

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: mockStatusData,
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isSandbox).toBe(true);
        expect(result.current.sandboxEnabled).toBe(true);
        expect(result.current.isLoading).toBe(false);
      });

      it('should provide sandbox status when sandbox is enabled but not active', async () => {
        const mockStatusData = {
          sandbox_mode: false,
          sandbox_enabled: true,
        };

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: mockStatusData,
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isSandbox).toBe(false);
        expect(result.current.sandboxEnabled).toBe(true);
        expect(result.current.isLoading).toBe(false);
      });

      it('should handle loading state', () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: undefined,
          isLoading: true,
          isSuccess: false,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isLoading).toBe(true);
        expect(result.current.isSandbox).toBe(false);
        expect(result.current.sandboxEnabled).toBe(false);
      });

      it('should default to false when data is undefined', () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: undefined,
          isLoading: false,
          isSuccess: false,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isSandbox).toBe(false);
        expect(result.current.sandboxEnabled).toBe(false);
      });
    });

    describe('toggleSandbox function', () => {
      it('should provide toggleSandbox function that calls mutation', async () => {
        const mockMutateAsync = vi.fn().mockResolvedValue({ sandbox_mode: true });

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: mockMutateAsync,
          isPending: false,
        } as any);

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

        await result.current.toggleSandbox(true);

        expect(mockMutateAsync).toHaveBeenCalledWith(true);
        expect(mockMutateAsync).toHaveBeenCalledTimes(1);
      });

      it('should call mutation with false to disable sandbox', async () => {
        const mockMutateAsync = vi.fn().mockResolvedValue({ sandbox_mode: false });

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: true, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: mockMutateAsync,
          isPending: false,
        } as any);

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

        await result.current.toggleSandbox(false);

        expect(mockMutateAsync).toHaveBeenCalledWith(false);
      });

      it('should propagate errors from mutation', async () => {
        const mockError = new Error('Failed to toggle sandbox');
        const mockMutateAsync = vi.fn().mockRejectedValue(mockError);

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: mockMutateAsync,
          isPending: false,
        } as any);

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

        await expect(result.current.toggleSandbox(true)).rejects.toThrow('Failed to toggle sandbox');
      });
    });

    describe('isToggling state', () => {
      it('should reflect mutation pending state as false', () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isToggling).toBe(false);
      });

      it('should reflect mutation pending state as true', () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: true,
        } as any);

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

        expect(result.current.isToggling).toBe(true);
      });
    });

    describe('localStorage synchronization', () => {
      beforeEach(() => {
        localStorageMock.clear();
      });

      it('should update localStorage when sandbox_mode is true', async () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: true, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

        renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        await waitFor(() => {
          expect(localStorageMock.getItem('sandbox_mode')).toBe('true');
        });
      });

      it('should update localStorage when sandbox_mode is false', async () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

        renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        await waitFor(() => {
          expect(localStorageMock.getItem('sandbox_mode')).toBe('false');
        });
      });

      it('should update localStorage when status changes from false to true', async () => {
        // First render with sandbox_mode = false
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

        const { unmount } = renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        await waitFor(() => {
          expect(localStorageMock.getItem('sandbox_mode')).toBe('false');
        });

        unmount();

        // Re-render with sandbox_mode = true
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: true, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        await waitFor(() => {
          expect(localStorageMock.getItem('sandbox_mode')).toBe('true');
        });
      });

      it('should not update localStorage when sandbox_mode is undefined', async () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: undefined,
          isLoading: true,
          isSuccess: false,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

        renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        // Wait a bit to ensure effect had time to run
        await new Promise(resolve => setTimeout(resolve, 100));

        expect(localStorageMock.getItem('sandbox_mode')).toBeNull();
      });

      it('should not update localStorage when status data is partial', async () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_enabled: true } as any,
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

        renderHook(() => useSandbox(), {
          wrapper: createWrapper(queryClient),
        });

        // Wait a bit to ensure effect had time to run
        await new Promise(resolve => setTimeout(resolve, 100));

        expect(localStorageMock.getItem('sandbox_mode')).toBeNull();
      });
    });

    describe('integration scenarios', () => {
      it('should handle complete toggle workflow', async () => {
        const mockMutateAsync = vi.fn().mockResolvedValue({ sandbox_mode: true });

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: mockMutateAsync,
          isPending: false,
        } as any);

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

        // Initial state
        expect(result.current.isSandbox).toBe(false);
        expect(result.current.isToggling).toBe(false);
        expect(localStorageMock.getItem('sandbox_mode')).toBe('false');

        // Toggle sandbox
        await result.current.toggleSandbox(true);

        expect(mockMutateAsync).toHaveBeenCalledWith(true);
      });

      it('should handle disabled sandbox feature', () => {
        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: false },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: vi.fn(),
          isPending: false,
        } as any);

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

        expect(result.current.isSandbox).toBe(false);
        expect(result.current.sandboxEnabled).toBe(false);
      });

      it('should handle multiple rapid toggle calls', async () => {
        const mockMutateAsync = vi.fn()
          .mockResolvedValueOnce({ sandbox_mode: true })
          .mockResolvedValueOnce({ sandbox_mode: false })
          .mockResolvedValueOnce({ sandbox_mode: true });

        vi.mocked(useSandboxStatus).mockReturnValue({
          data: { sandbox_mode: false, sandbox_enabled: true },
          isLoading: false,
          isSuccess: true,
          isError: false,
          error: null,
        } as any);

        vi.mocked(useToggleSandbox).mockReturnValue({
          mutateAsync: mockMutateAsync,
          isPending: false,
        } as any);

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

        // Multiple rapid calls
        await Promise.all([
          result.current.toggleSandbox(true),
          result.current.toggleSandbox(false),
          result.current.toggleSandbox(true),
        ]);

        expect(mockMutateAsync).toHaveBeenCalledTimes(3);
      });
    });
  });
});
