// Recommendation from the uuid library is to import get-random-values before
// uuid, so we're importing it first thing in the entry point.
// https://www.npmjs.com/package/uuid#react-native--expo
// eslint-disable-next-line simple-import-sort/imports
import "react-native-get-random-values";

import "@shopify/flash-list/jestSetup";

import mockBottomSheet from "@gorhom/bottom-sheet/mock";
import mockRNCNetInfo from "@react-native-community/netinfo/jest/netinfo-mock";
import inatjs from "inaturalistjs";
import fetchMock from "jest-fetch-mock";
import mockRNDeviceInfo from "react-native-device-info/jest/react-native-device-info-mock";
import mockSafeAreaContext from "react-native-safe-area-context/jest/mock";

import factory, { makeResponse } from "./factory";

// tests seem to be slower without this global reanimated mock
global.ReanimatedDataMock = {
  now: () => 0,
};

global.requestIdleCallback = callback => setTimeout( callback, 0 );
global.cancelIdleCallback = id => clearTimeout( id );

jest.mock( "react-native-volume-manager", () => ( {
  VolumeManager: {
    getVolume: jest.fn( () => Promise.resolve( 0.5 ) ),
    setVolume: jest.fn( ),
    addVolumeListener: jest.fn( () => ( { remove: jest.fn() } ) ),
    showNativeVolumeUI: jest.fn(),
  },
} ) );

// Mock the react-native-logs config because it has a dependency on AuthenticationService
// instead use console.logs for tests
jest.mock( "../react-native-logs.config", () => {
  const log = {
    extend: jest.fn( () => ( {
      debug: msg => console.debug( msg ),
      info: msg => console.info( msg ),
      warn: msg => console.warn( msg ),
      error: msg => console.error( msg ),
      debugWithExtra: ( ...args ) => console.debug( ...args ),
      infoWithExtra: ( ...args ) => console.info( ...args ),
      warnWithExtra: ( ...args ) => console.warn( ...args ),
      errorWithExtra: ( ...args ) => console.error( ...args ),
    } ) ),
  };
  return {
    log,
    legacyLogfilePath: "inaturalist-rn-log.txt",
    logFileNamePrefix: "inaturalist-rn-log",
    logFileDirectory: "logs",
    logWithoutRemote: log,
  };
} );

jest.mock( "@gorhom/bottom-sheet", () => ( {
  ...mockBottomSheet,
  __esModule: true,
} ) );
jest.mock( "@react-native-community/netinfo", () => mockRNCNetInfo );
jest.mock( "react-native-device-info", () => mockRNDeviceInfo );
jest.mock( "react-native-safe-area-context", () => mockSafeAreaContext );

// Reanimated 4.2 + Worklets 0.7: Jest loads native worklets which fails in Node. See:
// https://github.com/software-mansion/react-native-reanimated/discussions/8806
// we can remove this once the fix is released
jest.mock( "react-native-worklets", () => require( "react-native-worklets/src/mock" ) );

require( "react-native-reanimated" ).setUpTests();

// Some test environments may need a little more time
jest.setTimeout( 50000 );

// Mock inaturalistjs so we can make some fake responses
jest.mock( "inaturalistjs" );
inatjs.observations.observers.mockResolvedValue( makeResponse( ) );
inatjs.observations.search.mockResolvedValue( makeResponse( ) );
inatjs.observations.speciesCounts.mockResolvedValue( makeResponse( ) );
inatjs.observations.updates.mockResolvedValue( makeResponse( ) );
inatjs.users.projects.mockResolvedValue( makeResponse( ) );
inatjs.computervision.score_image.mockResolvedValue( makeResponse( ) );

// Set up mocked fetch for testing (or disabling) fetch requests
fetchMock.enableMocks( );
fetchMock.dontMock( );

const mockIconicTaxon = factory( "RemoteTaxon", {
  is_iconic: true,
  name: "Mock iconic taxon",
} );
inatjs.taxa.search.mockResolvedValue( makeResponse( [mockIconicTaxon] ) );

inatjs.announcements.search.mockResolvedValue( makeResponse( ) );
inatjs.observations.updates.mockResolvedValue( makeResponse( ) );

// the following two mocks are both needed for react-native-keep-awake
jest.mock( "@sayem314/react-native-keep-awake" );
jest.mock( "react-native/Libraries/TurboModule/TurboModuleRegistry", () => {
  const turboModuleRegistry = jest
    .requireActual( "react-native/Libraries/TurboModule/TurboModuleRegistry" );
  return {
    ...turboModuleRegistry,
    getEnforcing: name => {
      // List of TurboModules libraries to mock.
      const modulesToMock = ["ReactNativeKCKeepAwake", "FileReaderModule"];
      if ( modulesToMock.includes( name ) ) {
        return null;
      }
      return turboModuleRegistry.getEnforcing( name );
    },
  };
} );

jest.mock( "react-native-restart", ( ) => ( {
  restart: jest.fn( ),
} ) );

jest.mock( "@react-native-firebase/analytics", () => ( {
  getAnalytics: jest.fn( ),
  logEvent: jest.fn( ),
  setAnalyticsCollectionEnabled: jest.fn( ),
} ) );

jest.mock( "@react-native-firebase/perf", () => ( {
  startFirebaseTrace: jest.fn( ),
  stopFirebaseTrace: jest.fn( ),
  getPerformance: jest.fn( ( ) => ( {
    dataCollectionEnabled: true,
  } ) ),
} ) );

jest.mock( "react-native-legal", () => ( {
  launchLicenseListScreen: jest.fn(),
} ) );

// see https://stackoverflow.com/questions/42268673/jest-test-animated-view-for-react-native-app
// for more details about this withAnimatedTimeTravelEnabled approach. basically, this
// allows us to step through animation frames when a screen is first loading when we're using the
// FadeInView animation for navigation screen transitions
global.withAnimatedTimeTravelEnabled = ( options = {} ) => {
  beforeEach( () => {
    if ( !options.skipFakeTimers ) jest.useFakeTimers();
    jest.setSystemTime( new Date( 0 ) );
  } );
  if ( !options.skipFakeTimers ) {
    afterEach( () => {
      jest.useRealTimers();
    } );
  }
};

const frameTime = 10;
global.timeTravel = ( time = frameTime ) => {
  const tickTravel = () => {
    const now = Date.now();
    jest.setSystemTime( new Date( now + frameTime ) );
    jest.advanceTimersByTime( frameTime );
  };
  // Step through each of the frames
  const frames = time / frameTime;
  for ( let i = 0; i < frames; i += 1 ) {
    tickTravel();
  }
};

jest.mock( "sharedHelpers/installData", ( ) => ( {
  // For most tests it's just going to be burden to dismiss this thing that
  // most users only ever see once. If we do want to test it, we can redefine
  // this mock
  useOnboardingShown: jest.fn( ( ) => [true, jest.fn()] ),
  getInstallID: jest.fn( ( ) => "fake-installation-id" ),
} ) );

jest.mock( "components/SharedComponents/Buttons/Button", () => {
  const actualButton = jest.requireActual(
    "components/SharedComponents/Buttons/Button",
  ).default;
  // Use a very short debounce time (10ms) in tests to simulate the 300ms
  // debounce time in the actual Button component
  return jest.fn( props => actualButton( { ...props, debounceTime: 10 } ) );
} );

jest.mock( "navigation/FadeInView", () => {
  const React = require( "react" );
  const { View } = require( "react-native" );

  return jest.fn( ( { children } ) => React.createElement( View, null, children ) );
} );

jest.mock( "components/Camera/FadeInOutView", () => {
  const React = require( "react" );
  const { View } = require( "react-native" );

  return jest.fn( ( ) => React.createElement( View, null ) );
} );

// Disable bottom tab fade animation in tests to prevent act() warnings
jest.mock( "navigation/BottomTabNavigator/tabScreenOptions", () => ( {
  __esModule: true,
  default: {
    lazy: true,
    freezeOnBlur: true,
    headerShown: false,
    animation: "none",
  },
} ) );

// this silences console methods in jest tests, to make them less noisy
// and easier to debug. uncomment if you want to silence them
global.console = {
  ...console,
  // info: jest.fn(),
  // error: jest.fn(),
  // warn: jest.fn()
  // log: jest.fn()
};
