import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { Provider, useStore } from 'jotai';
import { userIdAtom } from 'src/atoms/app';
import { LocalStorageProvider } from 'src/contexts/localStorageContext';
import Logger from 'src/application/logging/logger';
import { UserIdentityService } from 'src/services/userIdentityService';
import { UserIdentityProvider, useUserIdentity } from '../userIdentityContext';

// 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 uuid to have predictable values in tests
vi.mock('uuid', () => ({
  v4: vi.fn(() => 'test-uuid-12345'),
}));

// Mock userIdService
const mockGetUserId = vi.fn().mockResolvedValue('spiffy-user-id-test-user-id');
const mockInitializeUser = vi.fn().mockResolvedValue(undefined);

const testUserIdService = {
  getUserIdOrDefault: mockGetUserId,
  initializeUser: mockInitializeUser,
  getUserIdOverrideFromLocalStorage: vi.fn().mockReturnValue(undefined),
} as unknown as UserIdentityService;

// Component that uses the useUserIdentity hook
const MockComponent: React.FC = () => {
  const context = useUserIdentity();
  const [userId, setUserId] = React.useState<string>('');

  React.useEffect(() => {
    context.getUserIdOrDefault().then(setUserId);
  }, [context]);

  return (
    <div data-testid="mock-component">
      <div data-testid="user-id">{userId}</div>
      <div data-testid="is-ready">{context.isReady.toString()}</div>
      <div data-testid="user-id-override">
        {context.getUserIdOverrideFromLocalStorage() || 'none'}
      </div>
    </div>
  );
};

// Component that directly reads from the userIdAtom to verify it's set
// Subscribes to atom changes to read it once it's set
const AtomReaderComponent: React.FC = () => {
  // Use useUserIdentity to ensure the atom is set
  useUserIdentity();
  const store = useStore();
  const [userId, setUserId] = React.useState<string>('not-set');

  React.useEffect(() => {
    // Subscribe to atom changes
    const unsubscribe = store.sub(userIdAtom, () => {
      try {
        const value = store.get(userIdAtom);
        if (value) {
          setUserId(value);
        }
      } catch {
        // Still not set, will try again on next change
      }
    });

    // Also try to read immediately after a delay
    const timer = setTimeout(() => {
      try {
        const value = store.get(userIdAtom);
        if (value) {
          setUserId(value);
        }
      } catch {
        // Not set yet, subscription will catch it when set
      }
    }, 50);

    return () => {
      unsubscribe();
      clearTimeout(timer);
    };
  }, [store]);

  return <div data-testid="atom-reader">{userId}</div>;
};

describe('UserIdentityProvider', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    // Clear localStorage before each test
    if (typeof localStorage !== 'undefined') {
      localStorage.clear();
    }
  });

  const renderWithProviders = (children: React.ReactNode) => {
    return render(
      <Provider>
        <LocalStorageProvider>
          <UserIdentityProvider userIdService={testUserIdService}>{children}</UserIdentityProvider>
        </LocalStorageProvider>
      </Provider>,
    );
  };

  describe('Provider Integration with Jotai Store - userIdAtom', () => {
    it('should set the userIdAtom when provider is mounted and useUserIdentity is called', async () => {
      renderWithProviders(
        <>
          <MockComponent />
          <AtomReaderComponent />
        </>,
      );

      // Wait for the atom to be set (useEffect in useUserIdentity runs after render)
      await waitFor(() => {
        const atomValue = screen.getByTestId('atom-reader').textContent;
        expect(atomValue).not.toBe('not-set');
        expect(atomValue).toContain('spiffy-user-id-');
      });

      // Verify the userId from context matches the atom value
      const contextUserId = screen.getByTestId('user-id').textContent;
      const atomUserId = screen.getByTestId('atom-reader').textContent;
      expect(contextUserId).toBe(atomUserId);
    });

    it('should set userIdAtom with override value when override exists in localStorage', async () => {
      // Set override before rendering
      localStorage.setItem('v1-spiffy-user-id-override', 'override-user-id-123');

      renderWithProviders(
        <>
          <MockComponent />
          <AtomReaderComponent />
        </>,
      );

      // Wait for the atom to be set with the override value
      await waitFor(() => {
        const atomValue = screen.getByTestId('atom-reader').textContent;
        expect(atomValue).toBe('override-user-id-123');
      });

      // Verify the userId from context matches the atom value
      const contextUserId = screen.getByTestId('user-id').textContent;
      const atomUserId = screen.getByTestId('atom-reader').textContent;
      expect(contextUserId).toBe(atomUserId);
    });

    it('should update userIdAtom when context becomes ready', async () => {
      // This test verifies that the atom is set even when context starts as not ready
      // We'll test with localStorage available from the start
      renderWithProviders(
        <>
          <MockComponent />
          <AtomReaderComponent />
        </>,
      );

      // Wait for context to be ready and atom to be set
      await waitFor(() => {
        const isReady = screen.getByTestId('is-ready').textContent;
        expect(isReady).toBe('true');
        const atomValue = screen.getByTestId('atom-reader').textContent;
        expect(atomValue).not.toBe('not-set');
      });
    });
  });

  describe('useUserIdentity Hook Integration', () => {
    it('should allow components to access context via useUserIdentity hook', async () => {
      renderWithProviders(<MockComponent />);

      await waitFor(() => {
        expect(screen.getByTestId('user-id')).toBeInTheDocument();
      });

      // Verify all context methods are accessible
      expect(screen.getByTestId('user-id').textContent).toContain('spiffy-user-id-');
      expect(screen.getByTestId('is-ready').textContent).toBe('true');
    });

    it('should throw error when used outside of UserIdentityProvider', () => {
      // Suppress console.error for this test
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

      expect(() => {
        render(
          <Provider>
            <MockComponent />
          </Provider>,
        );
      }).toThrow('useUserIdentity must be used within a UserIdentityProvider');

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

  describe('getUserIdOrDefault', () => {
    it('should return override userId if it exists', async () => {
      localStorage.setItem('v1-spiffy-user-id-override', 'override-123');

      renderWithProviders(<MockComponent />);

      await waitFor(() => {
        expect(screen.getByTestId('user-id').textContent).toBe('override-123');
      });
    });
  });

  describe('LocalStorage Operations', () => {
    it('should get userId override from localStorage', async () => {
      localStorage.setItem('v1-spiffy-user-id-override', 'test-override');

      renderWithProviders(<MockComponent />);

      await waitFor(() => {
        expect(screen.getByTestId('user-id-override').textContent).toBe('test-override');
      });
    });

    it('should set userId override in localStorage', async () => {
      const ComponentWithSet: React.FC = () => {
        const context = useUserIdentity();
        React.useEffect(() => {
          context.setUserIdOverrideInLocalStorage('new-override-id');
        }, [context]);

        return <div data-testid="set-component">Set</div>;
      };

      renderWithProviders(<ComponentWithSet />);

      await waitFor(() => {
        expect(localStorage.getItem('v1-spiffy-user-id-override')).toBe('new-override-id');
      });
    });

    it('should clear userId override from localStorage', async () => {
      localStorage.setItem('v1-spiffy-user-id-override', 'existing-override');

      const ComponentWithClear: React.FC = () => {
        const context = useUserIdentity();
        React.useEffect(() => {
          context.clearUserIdOverrideInLocalStorage();
        }, [context]);

        return <div data-testid="clear-component">Clear</div>;
      };

      renderWithProviders(<ComponentWithClear />);

      await waitFor(() => {
        expect(localStorage.getItem('v1-spiffy-user-id-override')).toBe('');
      });
    });

    it('should log info when setting userId override', async () => {
      const logSpy = vi.spyOn(Logger.prototype, 'logInfo');

      const ComponentWithSet: React.FC = () => {
        const context = useUserIdentity();
        React.useEffect(() => {
          context.setUserIdOverrideInLocalStorage('logged-override-id');
        }, [context]);

        return <div>Set</div>;
      };

      renderWithProviders(<ComponentWithSet />);

      await waitFor(() => {
        expect(logSpy).toHaveBeenCalledWith(
          'setUserIdOverrideInLocalStorage - Setting user_id=logged-override-id',
        );
      });
    });

    it('should log info when clearing userId override', async () => {
      const logSpy = vi.spyOn(Logger.prototype, 'logInfo');

      const ComponentWithClear: React.FC = () => {
        const context = useUserIdentity();
        React.useEffect(() => {
          context.clearUserIdOverrideInLocalStorage();
        }, [context]);

        return <div>Clear</div>;
      };

      renderWithProviders(<ComponentWithClear />);

      await waitFor(() => {
        expect(logSpy).toHaveBeenCalledWith('clearUserIdOverrideInLocalStorage - Clearing user_id');
      });
    });
  });
});
