/**
 * Biometrics Service Tests
 */

import { renderHook, act } from '@testing-library/react-hooks';
import { useBiometricStore, biometricService, getBiometricTypeName } from '../services/biometrics';
import * as LocalAuthentication from 'expo-local-authentication';
import * as SecureStore from 'expo-secure-store';

describe('useBiometricStore', () => {
  beforeEach(() => {
    useBiometricStore.setState({
      settings: {
        enabled: false,
        enabledAt: null,
        lastUsedAt: null,
      },
      capabilities: null,
    });

    jest.clearAllMocks();
  });

  describe('initial state', () => {
    it('should have correct initial state', () => {
      const { result } = renderHook(() => useBiometricStore());

      expect(result.current.settings.enabled).toBe(false);
      expect(result.current.settings.enabledAt).toBeNull();
      expect(result.current.settings.lastUsedAt).toBeNull();
      expect(result.current.capabilities).toBeNull();
    });
  });

  describe('enable', () => {
    it('should enable biometric login', () => {
      const { result } = renderHook(() => useBiometricStore());

      const beforeTime = Date.now();

      act(() => {
        result.current.enable();
      });

      expect(result.current.settings.enabled).toBe(true);
      expect(result.current.settings.enabledAt).toBeGreaterThanOrEqual(beforeTime);
    });
  });

  describe('disable', () => {
    it('should disable biometric login', () => {
      useBiometricStore.setState({
        settings: {
          enabled: true,
          enabledAt: Date.now(),
          lastUsedAt: Date.now(),
        },
      });

      const { result } = renderHook(() => useBiometricStore());

      act(() => {
        result.current.disable();
      });

      expect(result.current.settings.enabled).toBe(false);
      expect(result.current.settings.enabledAt).toBeNull();
      expect(result.current.settings.lastUsedAt).toBeNull();
    });
  });

  describe('updateLastUsed', () => {
    it('should update lastUsedAt timestamp', () => {
      const { result } = renderHook(() => useBiometricStore());

      const beforeTime = Date.now();

      act(() => {
        result.current.updateLastUsed();
      });

      expect(result.current.settings.lastUsedAt).toBeGreaterThanOrEqual(beforeTime);
    });
  });

  describe('setCapabilities', () => {
    it('should set capabilities', () => {
      const { result } = renderHook(() => useBiometricStore());

      const capabilities = {
        isAvailable: true,
        isEnrolled: true,
        types: ['fingerprint'] as any[],
        primaryType: 'fingerprint' as any,
        securityLevel: 2 as any,
      };

      act(() => {
        result.current.setCapabilities(capabilities);
      });

      expect(result.current.capabilities).toEqual(capabilities);
    });
  });
});

describe('biometricService', () => {
  beforeEach(() => {
    jest.clearAllMocks();

    useBiometricStore.setState({
      settings: {
        enabled: false,
        enabledAt: null,
        lastUsedAt: null,
      },
      capabilities: null,
    });
  });

  describe('checkCapabilities', () => {
    it('should check device capabilities', async () => {
      const capabilities = await biometricService.checkCapabilities();

      expect(LocalAuthentication.hasHardwareAsync).toHaveBeenCalled();
      expect(LocalAuthentication.isEnrolledAsync).toHaveBeenCalled();
      expect(LocalAuthentication.supportedAuthenticationTypesAsync).toHaveBeenCalled();
      expect(LocalAuthentication.getEnrolledLevelAsync).toHaveBeenCalled();

      expect(capabilities.isAvailable).toBe(true);
      expect(capabilities.isEnrolled).toBe(true);
    });

    it('should handle device without biometrics', async () => {
      (LocalAuthentication.hasHardwareAsync as jest.Mock).mockResolvedValue(false);
      (LocalAuthentication.isEnrolledAsync as jest.Mock).mockResolvedValue(false);

      const capabilities = await biometricService.checkCapabilities();

      expect(capabilities.isAvailable).toBe(false);
      expect(capabilities.isEnrolled).toBe(false);
    });
  });

  describe('authenticate', () => {
    it('should authenticate successfully', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: true,
      });

      const result = await biometricService.authenticate();

      expect(result.success).toBe(true);
      expect(LocalAuthentication.authenticateAsync).toHaveBeenCalled();
    });

    it('should handle user cancel', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: false,
        error: 'user_cancel',
      });

      const result = await biometricService.authenticate();

      expect(result.success).toBe(false);
      expect(result.error).toBe('Autenticación cancelada');
    });

    it('should handle lockout', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: false,
        error: 'lockout',
      });

      const result = await biometricService.authenticate();

      expect(result.success).toBe(false);
      expect(result.error).toContain('Demasiados intentos');
    });

    it('should handle not enrolled', async () => {
      (LocalAuthentication.hasHardwareAsync as jest.Mock).mockResolvedValue(true);
      (LocalAuthentication.isEnrolledAsync as jest.Mock).mockResolvedValue(false);

      const result = await biometricService.authenticate();

      expect(result.success).toBe(false);
      expect(result.error).toContain('No hay datos biométricos');
    });

    it('should handle no hardware', async () => {
      (LocalAuthentication.hasHardwareAsync as jest.Mock).mockResolvedValue(false);

      const result = await biometricService.authenticate();

      expect(result.success).toBe(false);
      expect(result.error).toContain('no soporta');
    });
  });

  describe('enableBiometricLogin', () => {
    it('should enable biometric login with token', async () => {
      const result = await biometricService.enableBiometricLogin('test-token');

      expect(result).toBe(true);
      expect(SecureStore.setItemAsync).toHaveBeenCalled();

      const state = useBiometricStore.getState();
      expect(state.settings.enabled).toBe(true);
    });

    it('should handle storage error', async () => {
      (SecureStore.setItemAsync as jest.Mock).mockRejectedValue(
        new Error('Storage error')
      );

      const result = await biometricService.enableBiometricLogin('test-token');

      expect(result).toBe(false);
    });
  });

  describe('disableBiometricLogin', () => {
    it('should disable biometric login', async () => {
      useBiometricStore.setState({
        settings: {
          enabled: true,
          enabledAt: Date.now(),
          lastUsedAt: Date.now(),
        },
      });

      const result = await biometricService.disableBiometricLogin();

      expect(result).toBe(true);
      expect(SecureStore.deleteItemAsync).toHaveBeenCalled();

      const state = useBiometricStore.getState();
      expect(state.settings.enabled).toBe(false);
    });
  });

  describe('loginWithBiometrics', () => {
    it('should login with biometrics successfully', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: true,
      });
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('stored-token');

      const result = await biometricService.loginWithBiometrics();

      expect(result.success).toBe(true);
      expect(result.token).toBe('stored-token');
    });

    it('should fail if no stored token', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: true,
      });
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);

      const result = await biometricService.loginWithBiometrics();

      expect(result.success).toBe(false);
      expect(result.error).toContain('No hay sesión');
    });

    it('should fail if authentication fails', async () => {
      (LocalAuthentication.authenticateAsync as jest.Mock).mockResolvedValue({
        success: false,
        error: 'user_cancel',
      });

      const result = await biometricService.loginWithBiometrics();

      expect(result.success).toBe(false);
    });
  });

  describe('isBiometricLoginAvailable', () => {
    it('should return true when all conditions met', async () => {
      useBiometricStore.setState({
        settings: {
          enabled: true,
          enabledAt: Date.now(),
          lastUsedAt: null,
        },
      });

      const available = await biometricService.isBiometricLoginAvailable();

      expect(available).toBe(true);
    });

    it('should return false when not enabled', async () => {
      useBiometricStore.setState({
        settings: {
          enabled: false,
          enabledAt: null,
          lastUsedAt: null,
        },
      });

      const available = await biometricService.isBiometricLoginAvailable();

      expect(available).toBe(false);
    });
  });

  describe('hasBiometricCredentials', () => {
    it('should return true when token exists', async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue('token');

      const hasCredentials = await biometricService.hasBiometricCredentials();

      expect(hasCredentials).toBe(true);
    });

    it('should return false when no token', async () => {
      (SecureStore.getItemAsync as jest.Mock).mockResolvedValue(null);

      const hasCredentials = await biometricService.hasBiometricCredentials();

      expect(hasCredentials).toBe(false);
    });
  });
});

describe('getBiometricTypeName', () => {
  it('should return correct name for fingerprint', () => {
    expect(getBiometricTypeName('fingerprint')).toContain('Huella');
  });

  it('should return correct name for facial', () => {
    const name = getBiometricTypeName('facial');
    expect(name === 'Face ID' || name.includes('Reconocimiento')).toBe(true);
  });

  it('should return correct name for iris', () => {
    expect(getBiometricTypeName('iris')).toContain('Iris');
  });

  it('should return default name for none', () => {
    expect(getBiometricTypeName('none')).toBe('Biométrico');
  });
});
