import React from 'react';
import { act, render, screen, waitFor } from '@testing-library/react';
import Logger from 'src/application/logging/logger';
import { useNewOrgConfig } from 'src/hooks/NewOrgConfig';
import { PromptButtonVariant } from '@envive-ai/react-toolkit-v3/PromptButton';
import { WidgetConfigProvider, useWidgetConfig } from '../widgetConfigContext';
import { WidgetConfigV3, WidgetTypeV3 } from '../../typesV3';

// Mock the Logger to avoid console output in tests
vi.spyOn(Logger.prototype, 'logInfo').mockImplementation(() => {});
vi.spyOn(Logger.prototype, 'logWarn').mockImplementation(() => {});
vi.spyOn(Logger.prototype, 'logError').mockImplementation(() => {});

// Mock the useNewOrgConfig hook
vi.mock('src/hooks/NewOrgConfig', () => ({
  useNewOrgConfig: vi.fn(),
}));

// Component that uses the useWidgetConfig hook
const MockWidgetConfigComponent: React.FC = () => {
  const { getWidgetConfig, isLoading } = useWidgetConfig();
  const [widgetConfig, setWidgetConfig] = React.useState<WidgetConfigV3 | null>(null);
  const [error, setError] = React.useState<string | null>(null);
  const [isLoadingState, setIsLoadingState] = React.useState(false);

  const handleGetConfig = async () => {
    setIsLoadingState(true);
    try {
      const config = await getWidgetConfig({
        widgetType: WidgetTypeV3.PromptCarouselV3,
        widgetConfigId: 'test-config-id',
      });
      setWidgetConfig(config);
      setError(null);
    } catch (err: unknown) {
      setError(err instanceof Error ? err.message : 'Unknown error');
      setWidgetConfig(null);
    } finally {
      setIsLoadingState(false);
    }
  };

  return (
    <div data-testid="widget-config-component">
      <div data-testid="is-loading">{isLoading.toString()}</div>
      <div data-testid="is-loading-state">{isLoadingState.toString()}</div>
      <div data-testid="has-widget-config">{widgetConfig ? 'true' : 'false'}</div>
      <div data-testid="widget-config-id">{widgetConfig?.widgetConfigId || 'not-set'}</div>
      <div data-testid="widget-config-type">{widgetConfig?.type || 'not-set'}</div>
      {error && <div data-testid="error-message">{error}</div>}
      <button
        data-testid="get-config-btn"
        type="button"
        onClick={handleGetConfig}
      >
        Get Config
      </button>
    </div>
  );
};

// Wrapper component for testing
const TestWrapper: React.FC<{
  children: React.ReactNode;
  mockNewOrgConfig?: {
    frontendConfig?: {
      widgetConfigs?: Record<string, WidgetConfigV3>;
    } | null;
    loading?: boolean;
  };
}> = ({ children, mockNewOrgConfig = {} }) => {
  const mockUseNewOrgConfig = useNewOrgConfig as ReturnType<typeof vi.fn>;
  mockUseNewOrgConfig.mockReturnValue({
    frontendConfig: mockNewOrgConfig.frontendConfig ?? null,
    loading: mockNewOrgConfig.loading ?? false,
  });

  return <WidgetConfigProvider>{children}</WidgetConfigProvider>;
};

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

  describe('useWidgetConfig Hook Integration', () => {
    it('should provide context through useWidgetConfig hook', async () => {
      const mockWidgetConfig: WidgetConfigV3 = {
        widgetConfigId: 'test-config-id',
        type: WidgetTypeV3.PromptCarouselV3,
        contentId: 'test-content-id',
      };

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': mockWidgetConfig,
              },
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
    });

    it('should throw error when used outside of WidgetConfigProvider', () => {
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

      const TestComponent: React.FC = () => {
        try {
          useWidgetConfig();
          return <div data-testid="no-error">No Error</div>;
        } catch (error: unknown) {
          return (
            <div data-testid="error">
              {error instanceof Error ? error.message : 'Unknown error'}
            </div>
          );
        }
      };

      render(<TestComponent />);

      expect(screen.getByTestId('error')).toHaveTextContent(
        'useWidgetConfig must be used within a WidgetConfigProvider',
      );

      consoleSpy.mockRestore();
    });
  });

  describe('getWidgetConfig', () => {
    it('should successfully retrieve widget config when config exists', async () => {
      const mockWidgetConfig: WidgetConfigV3 = {
        widgetConfigId: 'test-config-id',
        type: WidgetTypeV3.PromptCarouselV3,
        contentId: 'test-content-id',
        promptButtonType: PromptButtonVariant.LIGHT,
        boldFirstButton: true,
      };

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': mockWidgetConfig,
              },
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('has-widget-config')).toHaveTextContent('true');
        expect(screen.getByTestId('widget-config-id')).toHaveTextContent('test-config-id');
        expect(screen.getByTestId('widget-config-type')).toHaveTextContent(
          WidgetTypeV3.PromptCarouselV3,
        );
      });

      expect(screen.queryByTestId('error-message')).not.toBeInTheDocument();
    });

    it('should throw error when frontend config is loading', async () => {
      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': {
                  widgetConfigId: 'test-config-id',
                  type: WidgetTypeV3.PromptCarouselV3,
                },
              },
            },
            loading: true,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toHaveTextContent('Frontend config is loading');
      });
    });

    it('should throw error when widget config is not found', async () => {
      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {},
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toHaveTextContent(
          'Widget type: PromptCarouselV3 does not match widget config type: undefined',
        );
      });
    });

    it('should throw error when widget type does not match config type', async () => {
      // Create a mock config with wrong type for testing error handling
      const mockWidgetConfig = {
        widgetConfigId: 'test-config-id',
        type: WidgetTypeV3.ChatPreviewV3, // Different type - using type assertion for test
      } as unknown as WidgetConfigV3;

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': mockWidgetConfig,
              },
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toHaveTextContent(
          'Widget type: PromptCarouselV3 does not match widget config type: ChatPreviewV3',
        );
      });
    });

    it('should throw error when frontendConfig is null', async () => {
      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: null,
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('error-message')).toHaveTextContent(
          'Widget type: PromptCarouselV3 does not match widget config type: undefined',
        );
      });
    });
  });

  describe('isLoading State', () => {
    it('should be true when frontend config is loading', async () => {
      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {},
            },
            loading: true,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('is-loading')).toHaveTextContent('true');
      });
    });

    it('should be true when widget config is being retrieved', async () => {
      const mockWidgetConfig: WidgetConfigV3 = {
        widgetConfigId: 'test-config-id',
        type: WidgetTypeV3.PromptCarouselV3,
      };

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': mockWidgetConfig,
              },
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');

      // Click button and check loading state
      await act(async () => {
        getConfigButton.click();
      });

      // isLoadingState should be true during the async operation
      await waitFor(() => {
        expect(screen.getByTestId('is-loading-state')).toHaveTextContent('false');
      });
    });

    it('should be false when both frontend config and widget config are not loading', async () => {
      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {},
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('is-loading')).toHaveTextContent('false');
      });
    });
  });

  describe('Logger Integration', () => {
    it('should log info when retrieving widget config', async () => {
      const logDebugSpy = vi.spyOn(Logger.prototype, 'logDebug');
      const mockWidgetConfig: WidgetConfigV3 = {
        widgetConfigId: 'test-config-id',
        type: WidgetTypeV3.PromptCarouselV3,
      };

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'test-config-id': mockWidgetConfig,
              },
            },
            loading: false,
          }}
        >
          <MockWidgetConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('widget-config-component')).toBeInTheDocument();
      });

      const getConfigButton = screen.getByTestId('get-config-btn');
      await act(async () => {
        getConfigButton.click();
      });

      await waitFor(() => {
        expect(logDebugSpy).toHaveBeenCalledWith('retrieving widget config', {
          widgetConfigId: 'test-config-id',
          widgetType: WidgetTypeV3.PromptCarouselV3,
        });
        expect(logDebugSpy).toHaveBeenCalledWith(
          'returning widget config response',
          mockWidgetConfig,
        );
      });
    });
  });

  describe('Multiple Widget Config Retrievals', () => {
    it('should handle multiple sequential widget config retrievals', async () => {
      const mockWidgetConfig1: WidgetConfigV3 = {
        widgetConfigId: 'config-1',
        type: WidgetTypeV3.PromptCarouselV3,
      };
      const mockWidgetConfig2: WidgetConfigV3 = {
        widgetConfigId: 'config-2',
        type: WidgetTypeV3.PromptCarouselV3,
      };

      const TestComponent: React.FC = () => {
        const { getWidgetConfig } = useWidgetConfig();
        const [config1, setConfig1] = React.useState<WidgetConfigV3 | null>(null);
        const [config2, setConfig2] = React.useState<WidgetConfigV3 | null>(null);

        const handleGetConfigs = async () => {
          const c1 = await getWidgetConfig({
            widgetType: WidgetTypeV3.PromptCarouselV3,
            widgetConfigId: 'config-1',
          });
          setConfig1(c1);

          const c2 = await getWidgetConfig({
            widgetType: WidgetTypeV3.PromptCarouselV3,
            widgetConfigId: 'config-2',
          });
          setConfig2(c2);
        };

        return (
          <div>
            <div data-testid="config-1-id">{config1?.widgetConfigId || 'not-set'}</div>
            <div data-testid="config-2-id">{config2?.widgetConfigId || 'not-set'}</div>
            <button
              data-testid="get-configs-btn"
              type="button"
              onClick={handleGetConfigs}
            >
              Get Configs
            </button>
          </div>
        );
      };

      render(
        <TestWrapper
          mockNewOrgConfig={{
            frontendConfig: {
              widgetConfigs: {
                'config-1': mockWidgetConfig1,
                'config-2': mockWidgetConfig2,
              },
            },
            loading: false,
          }}
        >
          <TestComponent />
        </TestWrapper>,
      );

      const getConfigsButton = screen.getByTestId('get-configs-btn');
      await act(async () => {
        getConfigsButton.click();
      });

      await waitFor(() => {
        expect(screen.getByTestId('config-1-id')).toHaveTextContent('config-1');
        expect(screen.getByTestId('config-2-id')).toHaveTextContent('config-2');
      });
    });
  });
});
