import { commandHandlers as defaultCommandHandlers } from '@learnitive/command-handlers';
import { type LearnitiveAppCommand, learnitiveAppCommands } from '@learnitive/commands';
import type { CommandExposedServices } from '@learnitive/context';
import type {
  CommandDispatchService,
  CommandHandlerConfig,
} from '@learnitive/service-core';
import type { CommandArgs, RootEvents } from '@learnitive/types';
import { createTestEnvironment } from './test-service-setup';

type TestCommandHandlerArgs = {
  commands?: LearnitiveAppCommand[];
  commandHandlers?: CommandHandlerConfig[];
  target: CommandHandlerConfig;
  testEnvArgs?: Parameters<typeof createTestEnvironment>[0];
};
type TestCommandResult = Extract<
  RootEvents,
  { event: 'event::command:result' }
>['payload'];

type TestEnvironment = ReturnType<typeof createTestEnvironment>;
export type TestCommandHandlerReturnType = {
  testEnv: TestEnvironment;
  dispatch: <TId extends LearnitiveAppCommand['id']>(
    id: TId,
    args: CommandArgs<Extract<LearnitiveAppCommand, { id: TId }>>,
  ) => void;
  autoMountServices: () => Promise<CommandExposedServices>;
  getCommandResults: () => Array<TestCommandResult>;
};

export function testCommandHandler({
  commands = learnitiveAppCommands,
  commandHandlers = defaultCommandHandlers,
  target,
  testEnvArgs = {},
}: TestCommandHandlerArgs): TestCommandHandlerReturnType {
  const allCommandHandlers = commandHandlers.filter(
    (handler) => handler.id !== target.id,
  );
  allCommandHandlers.push(target);

  const testEnv = createTestEnvironment({
    ...testEnvArgs,
    commands,
    commandHandlers: allCommandHandlers,
  });

  let commandDispatcher: CommandDispatchService | undefined;

  const commandResults: Array<TestCommandResult> = [];

  return {
    testEnv,
    getCommandResults: () => commandResults,
    dispatch: (id, args) => {
      if (!commandDispatcher) {
        throw new Error('Command dispatcher not mounted');
      }
      commandDispatcher.dispatch(id, args, 'test');
    },
    autoMountServices: async () => {
      const services = testEnv.instantiateAll();

      commandDispatcher = services.commandDispatcher;

      await testEnv.mountAll();

      testEnv.rootEmitter.on(
        'event::command:result',
        (event) => {
          commandResults.push(event);
        },
        testEnv.commonOpts.rootAbortSignal,
      );
      return services;
    },
  };
}
