package com.github.sormuras.bach.workflow;

import com.github.sormuras.bach.Bach;
import com.github.sormuras.bach.Call;
import com.github.sormuras.bach.Workflow;
import com.github.sormuras.bach.internal.Durations;
import com.github.sormuras.bach.project.DeclaredModule;
import java.lang.System.Logger.Level;
import java.lang.module.ModuleFinder;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.spi.ToolProvider;
import jdk.jfr.Recording;
import jdk.jfr.consumer.RecordingFile;

public class ExecuteTestsWorkflow extends Workflow {

  public ExecuteTestsWorkflow(Bach bach) {
    super(bach);
  }

  public void execute() {
    var test = bach.project().spaces().test();
    if (test.modules().isEmpty()) {
      bach.log("Test module list is empty, nothing to do here.");
      return;
    }
    var modules = DeclaredModuleFinder.of(test.modules());

    var folders = bach.folders();
    var externalsFinder = ModuleFinder.of(folders.externalModules());
    var junitPlatformJFR = externalsFinder.find("org.junit.platform.jfr");
    var start = Instant.now();
    if (junitPlatformJFR.isEmpty()) {
      execute(modules, true, true);
      var duration = Durations.beautifyBetweenNow(start);
      bach.log(Level.INFO, "Execution of all tests took %s".formatted(duration));
    } else {
      var file = folders.workspace("junit-platform.jfr");
      try (var recording = new Recording()) {
        recording.setDestination(file);
        recording.setToDisk(false);
        recording.start();
        execute(modules, true, true);
        bach.log("Wrote flight-recording to " + file.toUri());
      } catch (Exception exception) {
        bach.logbook().log(exception);
      }
      var count = 0;
      try {
        var events = RecordingFile.readAllEvents(file);
        for (var event : events) {
          if ("org.junit.TestExecution".equals(event.getEventType().getName())) {
            count++;
          }
        }
      } catch (Exception exception) {
        bach.logbook().log(exception);
      }
      var s = count == 1 ? "" : "s";
      var duration = Durations.beautifyBetweenNow(start);
      bach.log(Level.INFO, "Execution of %d test%s took %s".formatted(count, s, duration));
    }
  }

  public void execute(DeclaredModuleFinder modules, boolean testsEnabled, boolean junitEnabled) {
    bach.log("Execute each test module");
    var junitPresent = bach.runner().findToolProvider("junit").isPresent();

    var runs = new ArrayList<Logbook.Run>();
    for (var module : modules.modules().toList()) {
      var name = module.name();
      bach.log(Level.INFO, "Execute tests in module %s".formatted(name));
      var finder = ModuleFinder.of(computeModulePaths(module).toArray(Path[]::new));
      // "test"
      if (testsEnabled)
        bach.runner()
            .streamToolProviders(finder, ModuleFinder.ofSystem(), true, name)
            .filter(provider -> provider.getClass().getModule().getName().equals(name))
            .filter(provider -> provider.name().equals("test"))
            .map(provider -> runTest(provider, List.of()))
            .forEach(runs::add);
      // "junit"
      if (junitEnabled && junitPresent)
        bach.runner()
            .streamToolProviders(finder, ModuleFinder.ofSystem(), true, name)
            .filter(provider -> provider.name().equals("junit"))
            .findFirst()
            .map(provider -> runJUnit(provider, name))
            .map(runs::add);
    }

    runs.forEach(Logbook.Run::requireSuccessful);
  }

  public List<Path> computeModulePaths(DeclaredModule module) {
    var descriptor = module.descriptor();
    var name = descriptor.name();
    var version = descriptor.version().orElse(project.version().value());
    var jar = name + "@" + version + "-test.jar";
    return List.of(
        bach.folders().workspace("modules-test", jar), // module under test
        bach.folders().workspace("modules"), // main modules
        bach.folders().workspace("modules-test"), // (more) test modules
        bach.folders().externalModules());
  }

  public Logbook.Run runTest(ToolProvider provider, List<String> arguments) {
    var providerClass = provider.getClass();
    var description = providerClass.getModule().getName() + "/" + providerClass.getName();
    bach.log(Level.INFO, "  %-9s %s", "test", description);
    return bach.runner().run(provider, arguments);
  }

  public Logbook.Run runJUnit(ToolProvider provider, String module) {
    var junit =
        Call.tool("junit")
            .with("--select-module", module)
            .with("--reports-dir", bach.folders().workspace("reports", "junit", module));
    bach.log(Level.INFO, "  %-9s %s", provider.name(), junit.toDescription(117));
    return bach.runner().run(provider, junit.arguments());
  }
}

@@ -107,16 +107,68 @@
 as.bach.
+Workflow;%0Aimport com.github.sormuras.bach.call.JUnit
 Call;%0Aim
@@ -193,32 +193,37 @@
 rmuras.bach.
-Workflow
+call.TestCall
 ;%0Aimport com
@@ -504,43 +504,8 @@
 st;%0A
-import java.util.spi.ToolProvider;%0A
 impo
@@ -3120,48 +3120,56 @@
 map(
-provider -%3E runTest(provider, List.of())
+TestCall::of)%0A            .map(bach::tweakAndRun
 )%0A  
@@ -3454,24 +3454,26 @@
             
+//
 .map(provide
@@ -3499,24 +3499,263 @@
 der, name))%0A
+            .map(JUnitCall::of)%0A            .map(junit -%3E junit%0A                .with(%22--select-module%22, name)%0A                .with(%22--reports-dir%22, bach.folders().workspace(%22reports%22, %22junit%22, name)))%0A            .map(bach::tweakAndRun)%0A
             
@@ -4319,32 +4319,32 @@
 e) test modules%0A
+
         bach.fol
@@ -4377,731 +4377,7 @@
 %0A  %7D
-%0A%0A  public Logbook.Run runTest(ToolProvider provider, List%3CString%3E arguments) %7B%0A    var providerClass = provider.getClass();%0A    var description = providerClass.getModule().getName() + %22/%22 + providerClass.getName();%0A    bach.log(Level.INFO, %22  %25-9s %25s%22, %22test%22, description);%0A    return bach.runner().run(provider, arguments);%0A  %7D%0A%0A  public Logbook.Run runJUnit(ToolProvider provider, String module) %7B%0A    var junit =%0A        Call.tool(%22junit%22)%0A            .with(%22--select-module%22, module)%0A            .with(%22--reports-dir%22, bach.folders().workspace(%22reports%22, %22junit%22, module));%0A    bach.log(Level.INFO, %22  %25-9s %25s%22, provider.name(), junit.toDescription(117));%0A    return bach.runner().run(provider, junit.arguments());%0A  %7D
 %0A%7D%0A

Use dedicated JUnit and Test calls