import React from 'react';
import { render, act, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { PersSDKProvider, usePersSDK, PersSDKContext } from '../providers/PersSDKProvider';

// Store SDK instances for event emission
let mockSdkInstance: any = null;

// Mock the SDK core module BEFORE any imports use it
jest.mock('@explorins/pers-sdk/core', () => {
  const createMockEventEmitter = () => {
    const subscribers: Array<{ callback: (event: any) => void; options?: any }> = [];
    
    return {
      subscribe: jest.fn((callback: (event: any) => void, options?: any) => {
        subscribers.push({ callback, options });
        return () => {
          const index = subscribers.findIndex(s => s.callback === callback);
          if (index > -1) subscribers.splice(index, 1);
        };
      }),
      emit: (event: any) => {
        subscribers.forEach(({ callback, options }) => {
          if (!options?.domains || options.domains.includes(event.domain)) {
            callback(event);
          }
        });
      },
      _subscribers: subscribers
    };
  };

  const MockPersSDK = jest.fn().mockImplementation(() => {
    const events = createMockEventEmitter();
    mockSdkInstance = {
      events,
      auth: {
        ensureValidToken: jest.fn().mockResolvedValue(true)
      },
      users: {
        getCurrentUser: jest.fn().mockResolvedValue({ id: 'refreshed-user', email: 'refreshed@test.com' })
      },
      restoreSession: jest.fn().mockResolvedValue(null)
    };
    return mockSdkInstance;
  });

  return {
    PersSDK: MockPersSDK,
    DefaultAuthProvider: jest.fn().mockImplementation(() => ({
      getToken: jest.fn().mockResolvedValue('mock-token')
    }))
  };
});

// Mock platform-specific providers
jest.mock('../providers/react-native-http-client', () => ({
  ReactNativeHttpClient: jest.fn().mockImplementation(() => ({}))
}));

jest.mock('../providers/react-native-auth-provider', () => ({
  createReactNativeAuthProvider: jest.fn().mockReturnValue({
    getToken: jest.fn().mockResolvedValue('mock-token')
  })
}));

jest.mock('../providers/rn-dpop-provider', () => ({
  ReactNativeDPoPProvider: jest.fn().mockImplementation(() => ({}))
}));

// Helper component to access context
const TestConsumer: React.FC<{ onContext: (ctx: PersSDKContext) => void }> = ({ onContext }) => {
  const context = usePersSDK();
  React.useEffect(() => {
    onContext(context);
  }, [context, onContext]);
  return <div data-testid="consumer">Consumer</div>;
};

// Helper to emit events on the mock SDK
const emitAuthEvent = (type: string, details: any = {}) => {
  if (mockSdkInstance?.events) {
    mockSdkInstance.events.emit({
      type,
      domain: 'authentication',
      details
    });
  }
};

describe('PersSDKProvider', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    mockSdkInstance = null;
  });

  describe('initialization', () => {
    it('should initialize with null SDK when no config provided', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext).not.toBeNull();
      });

      expect(capturedContext!.sdk).toBeNull();
      expect(capturedContext!.isInitialized).toBe(false);
      expect(capturedContext!.isAuthenticated).toBe(false);
      expect(capturedContext!.user).toBeNull();
    });

    it('should auto-initialize when config is provided', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.isInitialized).toBe(true);
      });

      expect(capturedContext!.sdk).not.toBeNull();
    });

    it('should prevent multiple initializations', async () => {
      const { PersSDK } = require('@explorins/pers-sdk/core');
      
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.isInitialized).toBe(true);
      });

      // Try to initialize again - should be ignored
      await act(async () => {
        await capturedContext!.initialize({ apiProjectKey: 'different-project' });
      });

      // Should only have been called once
      expect(PersSDK).toHaveBeenCalledTimes(1);
    });
  });

  describe('event handling', () => {
    it('should set user on session_restored event', async () => {
      let capturedContext: PersSDKContext | null = null;
      const mockUser = { id: 'user-123', email: 'test@example.com' };
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.sdk).not.toBeNull();
      });

      // Emit session_restored event
      await act(async () => {
        emitAuthEvent('session_restored', { user: mockUser });
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
        expect(capturedContext?.user).toEqual(mockUser);
      });
    });

    it('should set user on login_success event', async () => {
      let capturedContext: PersSDKContext | null = null;
      const mockUser = { id: 'user-456', email: 'login@example.com' };
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.sdk).not.toBeNull();
      });

      await act(async () => {
        emitAuthEvent('login_success', { user: mockUser });
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
        expect(capturedContext?.user).toEqual(mockUser);
      });
    });

    it('should clear user on logout_success event', async () => {
      let capturedContext: PersSDKContext | null = null;
      const mockUser = { id: 'user-789', email: 'logout@example.com' };
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.sdk).not.toBeNull();
      });

      // First, log in
      await act(async () => {
        emitAuthEvent('login_success', { user: mockUser });
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
      });

      // Then logout
      await act(async () => {
        emitAuthEvent('logout_success', {});
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(false);
        expect(capturedContext?.user).toBeNull();
      });
    });

    it('should clear user on session_restoration_failed event', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.sdk).not.toBeNull();
      });

      // Set initial authenticated state
      await act(async () => {
        capturedContext!.setAuthenticationState({ id: 'temp-user' } as any, true);
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
      });

      // Emit session_restoration_failed
      await act(async () => {
        emitAuthEvent('session_restoration_failed', { error: 'Token expired' });
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(false);
        expect(capturedContext?.user).toBeNull();
      });
    });

    it('should NOT respond to AUTH_FAILED code (dead code was removed)', async () => {
      // This test verifies that we no longer check for event.code === 'AUTH_FAILED'
      // which was dead code that never triggered
      let capturedContext: PersSDKContext | null = null;
      const mockUser = { id: 'user-stay', email: 'stay@example.com' };
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.sdk).not.toBeNull();
      });

      // Log in first
      await act(async () => {
        emitAuthEvent('login_success', { user: mockUser });
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
      });

      // Emit an event with code: 'AUTH_FAILED' - this should NOT clear user
      // because we removed the dead code that checked for this
      await act(async () => {
        if (mockSdkInstance?.events) {
          mockSdkInstance.events.emit({
            type: 'some_other_event',
            code: 'AUTH_FAILED', // This was the dead code path we removed
            domain: 'authentication',
            details: {}
          });
        }
      });

      // User should STILL be authenticated - the code: 'AUTH_FAILED' check is gone
      expect(capturedContext!.isAuthenticated).toBe(true);
      expect(capturedContext!.user).toEqual(mockUser);
    });
  });

  describe('usePersSDK hook', () => {
    it('should throw error when used outside provider', () => {
      // Suppress the expected error
      const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
      
      const TestComponent = () => {
        usePersSDK();
        return null;
      };

      expect(() => render(<TestComponent />)).toThrow(
        'usePersSDK must be used within a PersSDKProvider'
      );

      spy.mockRestore();
    });
  });

  describe('context methods', () => {
    it('should allow manual authentication state updates', async () => {
      let capturedContext: PersSDKContext | null = null;
      const mockUser = { id: 'manual-user', email: 'manual@example.com' };
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.isInitialized).toBe(true);
      });

      await act(async () => {
        capturedContext!.setAuthenticationState(mockUser as any, true);
      });

      await waitFor(() => {
        expect(capturedContext?.isAuthenticated).toBe(true);
        expect(capturedContext?.user).toEqual(mockUser);
      });
    });

    it('should refresh user data from SDK', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider config={{ apiProjectKey: 'test-project' }}>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext?.isInitialized).toBe(true);
      });

      await act(async () => {
        await capturedContext!.refreshUserData();
      });

      await waitFor(() => {
        expect(capturedContext?.user).toEqual({ id: 'refreshed-user', email: 'refreshed@test.com' });
      });
    });

    it('should throw error when refreshUserData called before init', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext).not.toBeNull();
      });

      await expect(capturedContext!.refreshUserData()).rejects.toThrow(
        'SDK not initialized'
      );
    });

    it('should throw error when restoreSession called before init', async () => {
      let capturedContext: PersSDKContext | null = null;
      
      render(
        <PersSDKProvider>
          <TestConsumer onContext={(ctx) => { capturedContext = ctx; }} />
        </PersSDKProvider>
      );

      await waitFor(() => {
        expect(capturedContext).not.toBeNull();
      });

      await expect(capturedContext!.restoreSession()).rejects.toThrow(
        'SDK not initialized'
      );
    });
  });
});
