import { type OAuthService } from '../services/oauth.service';
import { type TokenPair } from '../services/token.service';

// Mock passport-google-oauth20 before importing GoogleOAuthStrategy
vi.mock('passport-google-oauth20', () => {
  return {
    Strategy: class MockStrategy {
      constructor(_options: unknown, verify: (...args: unknown[]) => void) {
        // Store verify callback so we can call it in tests
        (this as any)._verify = verify;
      }
    },
  };
});

// Mock @nestjs/passport
vi.mock('@nestjs/passport', () => {
  return {
    PassportStrategy: (StrategyClass: any, _name?: string) => {
      return class extends StrategyClass {};
    },
  };
});

describe('GoogleOAuthStrategy', () => {
  const mockTokenPair: TokenPair = {
    accessToken: 'test-jwt',
    refreshToken: 'family.token',
    expiresIn: 900,
  };

  let mockOAuthService: { authenticateOAuth: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    vi.stubEnv('GOOGLE_CLIENT_ID', 'test-client-id');
    vi.stubEnv('GOOGLE_CLIENT_SECRET', 'test-client-secret');
    vi.stubEnv('GOOGLE_CALLBACK_URL', 'http://localhost:3001/auth/google/callback');

    mockOAuthService = {
      authenticateOAuth: vi.fn().mockResolvedValue(mockTokenPair),
    };
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it('creates strategy with dummy config when GOOGLE_CLIENT_ID is missing', async () => {
    vi.stubEnv('GOOGLE_CLIENT_ID', '');
    const { GoogleOAuthStrategy } = await import('../strategies/google-oauth.strategy');
    const strategy = new GoogleOAuthStrategy(mockOAuthService as unknown as OAuthService);
    // Strategy should still be created (graceful fallback with dummy values)
    expect(strategy).toBeDefined();
  });

  it('creates strategy with correct config', async () => {
    const { GoogleOAuthStrategy } = await import('../strategies/google-oauth.strategy');
    const strategy = new GoogleOAuthStrategy(mockOAuthService as unknown as OAuthService);
    expect(strategy).toBeDefined();
  });

  it('validate() calls oauthService.authenticateOAuth with correct profile', async () => {
    const { GoogleOAuthStrategy } = await import('../strategies/google-oauth.strategy');
    const strategy = new GoogleOAuthStrategy(mockOAuthService as unknown as OAuthService);

    const done = vi.fn();
    const googleProfile = {
      id: 'google-user-123',
      displayName: 'Test User',
      emails: [{ value: 'test@gmail.com', verified: true }],
      photos: [{ value: 'https://photo.url/pic.jpg' }],
      name: { givenName: 'Test', familyName: 'User' },
      _json: { sub: 'google-user-123', email: 'test@gmail.com' },
    };

    await strategy.validate('access-token', 'refresh-token', googleProfile as any, done);

    expect(mockOAuthService.authenticateOAuth).toHaveBeenCalledWith(
      expect.objectContaining({
        provider: 'GOOGLE',
        providerUserId: 'google-user-123',
        email: 'test@gmail.com',
        fullName: 'Test User',
        accessToken: 'access-token',
        refreshToken: 'refresh-token',
      }),
    );
    expect(done).toHaveBeenCalledWith(null, mockTokenPair);
  });

  it('validate() passes error to done callback on failure', async () => {
    mockOAuthService.authenticateOAuth.mockRejectedValue(new Error('OAuth failed'));

    const { GoogleOAuthStrategy } = await import('../strategies/google-oauth.strategy');
    const strategy = new GoogleOAuthStrategy(mockOAuthService as unknown as OAuthService);

    const done = vi.fn();
    await strategy.validate('token', 'refresh', { id: '1', displayName: 'X', _json: {} } as any, done);

    expect(done).toHaveBeenCalledWith(expect.any(Error), undefined);
  });
});
