import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { Provider, useAtomValue } from 'jotai';
import { EnviveConfigProvider } from 'src/contexts/enviveConfigContext/enviveConfigContext';
import { GraphQLProvider } from 'src/contexts/graphqlContext/graphqlContext';
import { LocalStorageProvider } from 'src/contexts/localStorageContext/localStorageContext';
import { newOrgConfigAtom } from 'src/atoms/org/newOrgConfigAtom';
import { orgShortNameAtom } from 'src/atoms/envive/enviveConfig';
import { useUIConfig } from 'src/hooks/GraphQLConfig/useGraphQLConfig';
import { NewOrgConfigProvider, useNewOrgConfigContext } from '../newOrgConfigContext';

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

// Component that uses the useNewOrgConfigContext hook
const MockNewOrgConfigComponent: React.FC = () => {
  const config = useNewOrgConfigContext();

  return (
    <div data-testid="new-org-config-component">
      <div data-testid="loading">{config.loading.toString()}</div>
      <div data-testid="has-error">{config.error ? 'true' : 'false'}</div>
      <div data-testid="has-colors-config">{config.colorsConfig ? 'true' : 'false'}</div>
      <div data-testid="has-frontend-config">{config.frontendConfig ? 'true' : 'false'}</div>
      {config.error && <div data-testid="error-message">{config.error.message}</div>}
    </div>
  );
};

// Component that reads the atom directly to verify it's set
const AtomReaderComponent: React.FC = () => {
  const config = useAtomValue(newOrgConfigAtom);
  const orgShortName = useAtomValue(orgShortNameAtom);

  return (
    <div data-testid="atom-reader">
      <div data-testid="atom-org-short-name">{orgShortName || 'not-set'}</div>
      <div data-testid="atom-has-config">{config ? 'has-config' : 'no-config'}</div>
      {config && (
        <>
          <div data-testid="atom-has-colors">{config.colorsConfig ? 'true' : 'false'}</div>
          <div data-testid="atom-has-frontend">{config.frontendConfig ? 'true' : 'false'}</div>
        </>
      )}
    </div>
  );
};

// Wrapper component to ensure providers are in correct order
const TestWrapper: React.FC<{
  children: React.ReactNode;
  orgShortName?: string;
  mockHookData?: {
    data?: any;
    loading?: boolean;
    error?: Error | null;
  };
}> = ({ children, orgShortName, mockHookData = {} }) => {
  // Mock the hook
  const mockUseUIConfig = useUIConfig as any;
  mockUseUIConfig.mockReturnValue({
    data: mockHookData.data || {},
    loading: mockHookData.loading ?? false,
    error: mockHookData.error || null,
    refetch: vi.fn(),
  });

  return (
    <Provider>
      <EnviveConfigProvider
        identifyingPrefix="test"
        orgShortName={orgShortName}
        baseUrl="https://test-api.example.com"
        orgLevelApiKey="test-key"
      >
        <LocalStorageProvider>
          <GraphQLProvider>
            <NewOrgConfigProvider>{children}</NewOrgConfigProvider>
          </GraphQLProvider>
        </LocalStorageProvider>
      </EnviveConfigProvider>
    </Provider>
  );
};

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

  describe('useNewOrgConfigContext Hook Integration', () => {
    it('should provide context through useNewOrgConfigContext hook', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: { merchantOverrideCss: 'test-css' },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

      expect(screen.getByTestId('new-org-config-component')).toBeInTheDocument();
      expect(screen.getByTestId('has-colors-config')).toHaveTextContent('true');
      expect(screen.getByTestId('has-frontend-config')).toHaveTextContent('true');
    });

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

      const TestComponent: React.FC = () => {
        try {
          useNewOrgConfigContext();
          return <div data-testid="no-error">No Error</div>;
        } catch (error: any) {
          return <div data-testid="error">{error.message}</div>;
        }
      };

      render(
        <Provider>
          <TestComponent />
        </Provider>,
      );

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

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

  describe('Loading State', () => {
    it('should show loading state when hook is loading', async () => {
      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ loading: true, data: {} }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

    it('should show loading state when orgShortName is not available', async () => {
      render(
        <TestWrapper mockHookData={{ data: {}, loading: false }}>
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

    it('should not show loading when orgShortName is available and hook is not loading', async () => {
      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: {}, loading: false }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

  describe('Error Handling', () => {
    it('should handle errors from useUIConfig', async () => {
      const testError = new Error('Failed to fetch config');

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ error: testError, loading: false, data: {} }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

      expect(screen.getByTestId('error-message')).toHaveTextContent('Failed to fetch config');
      expect(screen.getByTestId('loading')).toHaveTextContent('false');
    });

    it('should return error state without throwing', async () => {
      const testError = new Error('Network error');

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ error: testError, loading: false, data: {} }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

      // Component should still render without crashing
      expect(screen.getByTestId('new-org-config-component')).toBeInTheDocument();
    });
  });

  describe('Atom Integration', () => {
    it('should set newOrgConfigAtom when data is available', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000', accentSecondary: '#00FF00' },
        frontendConfig: { merchantOverrideCss: 'test-css' },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData, loading: false }}
        >
          <AtomReaderComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('atom-org-short-name')).toHaveTextContent('test-org');
      });

      await waitFor(
        () => {
          expect(screen.getByTestId('atom-has-config')).toHaveTextContent('has-config');
        },
        { timeout: 2000 },
      );

      expect(screen.getByTestId('atom-has-colors')).toHaveTextContent('true');
      expect(screen.getByTestId('atom-has-frontend')).toHaveTextContent('true');
    });

    it('should not set atom when orgShortName is missing', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: {},
      };

      render(
        <TestWrapper mockHookData={{ data: mockData, loading: false }}>
          <AtomReaderComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('atom-org-short-name')).toHaveTextContent('not-set');
      });

      // Atom should not be set when orgShortName is missing
      await waitFor(() => {
        const hasConfig = screen.getByTestId('atom-has-config').textContent;
        // The atom might be set to null or not set at all
        expect(hasConfig).toBeDefined();
      });
    });

    it('should not set atom when loading', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000' },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData, loading: true }}
        >
          <AtomReaderComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('atom-org-short-name')).toHaveTextContent('test-org');
      });

      // When loading, the atom setter might not be called
      // The context value will have loading: true
    });

    it('should set atom with initial data', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: { merchantOverrideCss: 'test-css' },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData, loading: false }}
        >
          <AtomReaderComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('atom-has-config')).toHaveTextContent('has-config');
      });

      expect(screen.getByTestId('atom-has-colors')).toHaveTextContent('true');
      expect(screen.getByTestId('atom-has-frontend')).toHaveTextContent('true');
    });
  });

  describe('Context Value', () => {
    it('should provide colorsConfig and frontendConfig in context', async () => {
      const mockData = {
        colorsConfig: {
          accentPrimary: '#FF0000',
          accentSecondary: '#00FF00',
          backgroundDark: '#000000',
        },
        frontendConfig: {
          merchantOverrideCss: 'test-css',
        },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData, loading: false }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('has-colors-config')).toHaveTextContent('true');
      });

      expect(screen.getByTestId('has-frontend-config')).toHaveTextContent('true');
    });

    it('should provide empty config when data is empty', async () => {
      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: {}, loading: false }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

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

      expect(screen.getByTestId('has-colors-config')).toHaveTextContent('false');
      expect(screen.getByTestId('has-frontend-config')).toHaveTextContent('false');
    });

    it('should merge loading and error states with config data', async () => {
      const mockData = {
        colorsConfig: { accentPrimary: '#FF0000' },
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: mockData, loading: false }}
        >
          <MockNewOrgConfigComponent />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('loading')).toHaveTextContent('false');
        expect(screen.getByTestId('has-error')).toHaveTextContent('false');
        expect(screen.getByTestId('has-colors-config')).toHaveTextContent('true');
      });
    });
  });

  describe('FeatureFlagServiceProvider Integration', () => {
    it('should wrap children with FeatureFlagServiceProvider', async () => {
      const TestChild: React.FC = () => {
        // This should work because FeatureFlagServiceProvider is wrapping it
        return <div data-testid="child">Child Component</div>;
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: {}, loading: false }}
        >
          <TestChild />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('child')).toBeInTheDocument();
      });

      // Verify the child is rendered (which means FeatureFlagServiceProvider is working)
      expect(screen.getByTestId('child')).toHaveTextContent('Child Component');
    });
  });

  describe('Context Value Memoization', () => {
    it('should maintain stable context value references', async () => {
      let contextValue1: any;
      let contextValue2: any;

      const Component1: React.FC = () => {
        const context = useNewOrgConfigContext();
        contextValue1 = context;
        return <div>Component1</div>;
      };

      const Component2: React.FC = () => {
        const context = useNewOrgConfigContext();
        contextValue2 = context;
        return <div>Component2</div>;
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: {}, loading: false }}
        >
          <Component1 />
          <Component2 />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(contextValue1).toBeDefined();
        expect(contextValue2).toBeDefined();
        // Both components should receive the same context instance
        expect(contextValue1).toBe(contextValue2);
      });
    });
  });
  describe('Override Config', () => {
    it('should use overrideConfig when provided instead of fetched config', async () => {
      const fetchedData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: { merchantOverrideCss: 'fetched-css' },
      };

      const overrideData = {
        colorsConfig: { accentPrimary: '#0000FF' },
        frontendConfig: { merchantOverrideCss: 'override-css' },
        orgPageConfig: { pageVariants: [] },
      };

      // Updated TestWrapper to accept overrideConfig
      const TestWrapperWithOverride: React.FC<{
        children: React.ReactNode;
        orgShortName?: string;
        mockHookData?: any;
        overrideConfig?: any;
      }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
        const mockUseUIConfig = useUIConfig as any;
        mockUseUIConfig.mockReturnValue({
          data: mockHookData.data || {},
          loading: mockHookData.loading ?? false,
          error: mockHookData.error || null,
          refetch: vi.fn(),
        });

        return (
          <Provider>
            <EnviveConfigProvider
              identifyingPrefix="test"
              orgShortName={orgShortName}
              baseUrl="https://test-api.example.com"
              orgLevelApiKey="test-key"
            >
              <LocalStorageProvider>
                <GraphQLProvider>
                  <NewOrgConfigProvider overrideConfig={overrideConfig}>
                    {children}
                  </NewOrgConfigProvider>
                </GraphQLProvider>
              </LocalStorageProvider>
            </EnviveConfigProvider>
          </Provider>
        );
      };

      const ConfigChecker: React.FC = () => {
        const config = useNewOrgConfigContext();
        return (
          <div data-testid="config-checker">
            <div data-testid="accent-color">{config.colorsConfig?.accentPrimary || 'none'}</div>
            <div data-testid="css-value">
              {config.frontendConfig?.merchantOverrideCss || 'none'}
            </div>
          </div>
        );
      };

      render(
        <TestWrapperWithOverride
          orgShortName="test-org"
          mockHookData={{ data: fetchedData, loading: false }}
          overrideConfig={overrideData}
        >
          <ConfigChecker />
        </TestWrapperWithOverride>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('accent-color')).toHaveTextContent('#0000FF');
      });

      // Should use override config, not fetched config
      expect(screen.getByTestId('css-value')).toHaveTextContent('override-css');
    });

    it('should fall back to fetched config when overrideConfig is not provided', async () => {
      const fetchedData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: { merchantOverrideCss: 'fetched-css' },
      };

      const ConfigChecker: React.FC = () => {
        const config = useNewOrgConfigContext();
        return (
          <div data-testid="config-checker">
            <div data-testid="accent-color">{config.colorsConfig?.accentPrimary || 'none'}</div>
            <div data-testid="css-value">
              {config.frontendConfig?.merchantOverrideCss || 'none'}
            </div>
          </div>
        );
      };

      render(
        <TestWrapper
          orgShortName="test-org"
          mockHookData={{ data: fetchedData, loading: false }}
        >
          <ConfigChecker />
        </TestWrapper>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('accent-color')).toHaveTextContent('#FF0000');
      });

      expect(screen.getByTestId('css-value')).toHaveTextContent('fetched-css');
    });

    it('should set newOrgConfigAtom with overrideConfig when provided', async () => {
      const fetchedData = {
        colorsConfig: { accentPrimary: '#FF0000' },
      };

      const overrideData = {
        colorsConfig: { accentPrimary: '#00FF00' },
        frontendConfig: { merchantOverrideCss: 'override-css' },
      };

      const TestWrapperWithOverride: React.FC<{
        children: React.ReactNode;
        orgShortName?: string;
        mockHookData?: any;
        overrideConfig?: any;
      }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
        const mockUseUIConfig = useUIConfig as any;
        mockUseUIConfig.mockReturnValue({
          data: mockHookData.data || {},
          loading: mockHookData.loading ?? false,
          error: mockHookData.error || null,
          refetch: vi.fn(),
        });

        return (
          <Provider>
            <EnviveConfigProvider
              identifyingPrefix="test"
              orgShortName={orgShortName}
              baseUrl="https://test-api.example.com"
              orgLevelApiKey="test-key"
            >
              <LocalStorageProvider>
                <GraphQLProvider>
                  <NewOrgConfigProvider overrideConfig={overrideConfig}>
                    {children}
                  </NewOrgConfigProvider>
                </GraphQLProvider>
              </LocalStorageProvider>
            </EnviveConfigProvider>
          </Provider>
        );
      };

      render(
        <TestWrapperWithOverride
          orgShortName="test-org"
          mockHookData={{ data: fetchedData, loading: false }}
          overrideConfig={overrideData}
        >
          <AtomReaderComponent />
        </TestWrapperWithOverride>,
      );

      await waitFor(() => {
        expect(screen.getByTestId('atom-has-config')).toHaveTextContent('has-config');
      });

      // Atom should have the override config values
      expect(screen.getByTestId('atom-has-colors')).toHaveTextContent('true');
      expect(screen.getByTestId('atom-has-frontend')).toHaveTextContent('true');
    });

    it('should handle undefined overrideConfig gracefully', async () => {
      const fetchedData = {
        colorsConfig: { accentPrimary: '#FF0000' },
        frontendConfig: { merchantOverrideCss: 'fetched-css' },
      };

      const TestWrapperWithOverride: React.FC<{
        children: React.ReactNode;
        orgShortName?: string;
        mockHookData?: any;
        overrideConfig?: any;
      }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
        const mockUseUIConfig = useUIConfig as any;
        mockUseUIConfig.mockReturnValue({
          data: mockHookData.data || {},
          loading: mockHookData.loading ?? false,
          error: mockHookData.error || null,
          refetch: vi.fn(),
        });

        return (
          <Provider>
            <EnviveConfigProvider
              identifyingPrefix="test"
              orgShortName={orgShortName}
              baseUrl="https://test-api.example.com"
              orgLevelApiKey="test-key"
            >
              <LocalStorageProvider>
                <GraphQLProvider>
                  <NewOrgConfigProvider overrideConfig={overrideConfig}>
                    {children}
                  </NewOrgConfigProvider>
                </GraphQLProvider>
              </LocalStorageProvider>
            </EnviveConfigProvider>
          </Provider>
        );
      };

      render(
        <TestWrapperWithOverride
          orgShortName="test-org"
          mockHookData={{ data: fetchedData, loading: false }}
          overrideConfig={undefined}
        >
          <MockNewOrgConfigComponent />
        </TestWrapperWithOverride>,
      );

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

      // Should fall back to fetched data
      expect(screen.getByTestId('has-colors-config')).toHaveTextContent('true');
      expect(screen.getByTestId('has-frontend-config')).toHaveTextContent('true');
    });
  });
});
