/**
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

import {TestSuite} from '@rnoh/testerino';
import {
  SampleTurboModule,
  GeneratedSampleTurboModule,
  SampleWorkerTurboModule,
  CodegenLibSampleTurboModule,
} from 'react-native-sample-package';
import {
  SomeEnum1,
  SomeEnum2,
  SomeEnum3,
} from 'react-native-sample-package/src/specs/turbo-modules/NativeGeneratedSampleTurboModule';
import {Button, TestCase} from '../components';
import {ScrollView, Platform, DeviceEventEmitter} from 'react-native';

async function sleep(durationMs: number): Promise<void> {
  return new Promise<void>(resolve => {
    setTimeout(resolve, durationMs);
  });
}

export function TurboModuleTest() {
  return (
    <TestSuite name="TurboModule">
      <TestSuite name="manual implementation">
        <TestCase.Logical
          itShould="return null when calling getNull()"
          fn={({expect}) => {
            expect(SampleTurboModule.getNull(null)).to.be.null;
          }}
        />
        <TestCase.Logical
          itShould="return [1, 2, 3] when calling getArray"
          fn={({expect}) => {
            expect(SampleTurboModule.getArray([1, 2, 3])).to.eql([1, 2, 3]);
          }}
        />
        <TestCase.Logical
          itShould="call the passed callback twice"
          tags={['sequential']}
          fn={async ({expect}) => {
            let sum = 0;
            let timesCalled = 0;

            SampleTurboModule.getValueWithCallback((value: string) => {
              sum += parseInt(value, 10);
              timesCalled++;
            });

            await sleep(1000);

            expect(sum).to.equal(5);
            expect(timesCalled).to.equal(2);
          }}
        />
        <TestCase.Logical
          itShould="return expect result when calling getValueWithPromise"
          fn={async ({expect}) => {
            const result = await SampleTurboModule.getValueWithPromise(false);
            expect(result).to.equal('result!');
          }}
        />
        <CommonTurboModuleTestCases sampleTurboModule={SampleTurboModule} />
      </TestSuite>
      <TestSuite name="generated">
        <CommonTurboModuleTestCases
          sampleTurboModule={GeneratedSampleTurboModule}
        />
        <TestCase.Logical
          itShould="get union value"
          fn={async ({expect}) => {
            expect(GeneratedSampleTurboModule.getUnionValue('foo')).to.be.eq(
              'foo',
            );
          }}
        />
        <TestCase.Logical
          itShould="support enums"
          fn={async ({expect}) => {
            const result = GeneratedSampleTurboModule.getEnum(
              SomeEnum1.FOO,
              SomeEnum2.FOO,
              SomeEnum3.FOO,
            );
            expect(result.enum1).to.be.eq(SomeEnum1.FOO);
            expect(result.enum2).to.be.eq(SomeEnum2.FOO);
            expect(result.enum3).to.be.eq(SomeEnum3.FOO);
          }}
        />
        <TestCase.Logical
          itShould="handle enums without specified values correctly"
          skip="RN codegen doesn't parse enums without explicit values correctly"
          fn={async ({expect}) => {
            const result = GeneratedSampleTurboModule.getEnum(
              SomeEnum1.FOO,
              SomeEnum2.FOO,
              SomeEnum3.FOO,
            );
            expect(result.hardcodedEnum1 === SomeEnum1.FOO).to.be.true;
          }}
        />
        <TestCase.Logical
          itShould="calculate fibonacci numbers on the Worker thread"
          fn={async ({expect}) => {
            const result =
              await GeneratedSampleTurboModule.calculateOnWorker(25);
            expect(result).to.equal(75025);
          }}
        />
      </TestSuite>
      <TestSuite name="WorkerTurboModule">
        <TestCase.Logical
          itShould="support workerTM 2 workerTM communication (WorkerRNInstance::getWorkerTurboModule)"
          fn={async ({expect}) => {
            expect(
              await SampleWorkerTurboModule.testCommunicatingWithOtherWorkerTurboModules(),
            ).to.be.true;
          }}
        />
        <TestCase.Logical
          itShould="support worker -> cpp communication (WorkerRNInstance::postMessageToCpp)"
          fn={async ({expect}) => {
            expect(
              await SampleWorkerTurboModule.testCommunicationBetweenWorkerAndCpp(),
            ).to.be.true;
          }}
        />
        <TestCase.Logical
          itShould="support WorkerRNInstance::getArchitecture"
          fn={({expect}) => {
            expect(SampleWorkerTurboModule.getArchitecture()).to.be.eq(
              (Platform.constants as any).rnohArchitecture,
            );
          }}
        />
        <TestCase.Logical
          itShould="not crash"
          fn={({}) => {
            SampleWorkerTurboModule.runComputeIntensiveTask();
          }}
        />
        <TestCase.Logical
          itShould="support async methods"
          fn={async ({expect}) => {
            const result = await SampleWorkerTurboModule.runAsyncTask();
            expect(result).to.be.eq(42);
          }}
        />
        <TestCase.Example
          modal
          itShould="not impact the performance of ScrollView when running intensive task">
          <ScrollView
            style={{height: 256}}
            contentContainerStyle={{height: 384, justifyContent: 'center'}}>
            <Button
              label="Run task"
              onPress={() => {
                SampleWorkerTurboModule.runComputeIntensiveTask();
              }}
            />
          </ScrollView>
        </TestCase.Example>
        <TestCase.Logical
          itShould="emit device event"
          fn={async ({expect}) => {
            const result = await new Promise(resolve => {
              const subscription = DeviceEventEmitter.addListener(
                'WORKER_TURBO_MODULE_TEST',
                p => {
                  resolve(p);
                  subscription.remove();
                },
              );
              SampleWorkerTurboModule.emitDeviceEvent(
                'WORKER_TURBO_MODULE_TEST',
                42,
              );
            });
            expect(result).to.be.eq(42);
          }}
        />
        <TestSuite name="runOnUIThread">
          <TestCase.Logical
            itShould="support running tasks on UI thread from worker"
            fn={async ({expect}) => {
              const result =
                await SampleWorkerTurboModule.getRNVersionFromUIContext('RN ');
              const rnVersion = Platform.constants.reactNativeVersion;
              expect(result).to.be.eq(
                `RN ${rnVersion.major}.${rnVersion.minor}.${rnVersion.patch}`,
              );
            }}
          />
          <TestCase.Example itShould="show alert">
            <Button
              label="Show Alert"
              onPress={() => {
                SampleWorkerTurboModule.showAlert();
              }}
            />
          </TestCase.Example>
        </TestSuite>
      </TestSuite>
      <TestSuite name="generated by codegen-lib-harmony">
        <TestCase.Logical
          itShould="not crash when calling method of Turbo Module partially generated by this command"
          fn={({expect}) => {
            expect(CodegenLibSampleTurboModule.getNumber()).to.be.eq(42);
          }}
        />
      </TestSuite>
    </TestSuite>
  );
}

function CommonTurboModuleTestCases({
  sampleTurboModule,
}: {
  sampleTurboModule: typeof GeneratedSampleTurboModule;
}) {
  return (
    <>
      <TestCase.Logical
        itShould="return null when calling voidFunc()"
        fn={({expect}) => {
          expect(sampleTurboModule.voidFunc()).to.be.null;
        }}
      />
      <TestCase.Logical
        itShould="return true when calling getBool(true)"
        fn={({expect}) => {
          expect(sampleTurboModule.getBool(true)).to.be.true;
        }}
      />

      <TestCase.Logical
        itShould="return { x: { y: 1 } } when calling getObject"
        fn={({expect}) => {
          expect(sampleTurboModule.getObject({x: {y: 1}})).to.eql({
            x: {y: 1},
          });
        }}
      />
      <TestCase.Logical
        itShould="call back the callback that returns a string"
        fn={async ({expect}) => {
          const promise = new Promise<string>(resolve => {
            sampleTurboModule.registerFunction(resolve);
          });
          expect(typeof (await promise)).to.be.eq('string');
        }}
      />
      <TestCase.Logical
        itShould="handle async jobs"
        fn={async ({expect}) => {
          const result = await sampleTurboModule?.doAsyncJob(true);
          expect(typeof result).to.be.eq('string');
        }}
      />
      <TestCase.Logical
        itShould="handle errors in async jobs"
        fn={async ({expect}) => {
          let errMsg: string | undefined;
          try {
            await sampleTurboModule?.doAsyncJob(false);
          } catch (err) {
            errMsg = (err as Error).message;
          }

          expect(errMsg).to.be.eq('rejected on native side');
        }}
      />
      <TestCase.Logical
        itShould="get an array asynchronously"
        fn={async ({expect}) => {
          expect(await sampleTurboModule.getPromisedArray()).to.be.eql([
            1, 2, 3,
          ]);
        }}
      />
    </>
  );
}