{#- Java test file template for JUnit 5

   Context variables:
   - header: file header comment
   - java_group_id: Java package ID (e.g., "com.example.e2e")
   - test_class_name: test class name (e.g., "BasicTest")
   - category: category name from fixture group
   - imports: list of import strings (pre-formatted)
   - needs_object_mapper: boolean
   - fixtures_body: string containing all rendered test methods
   - uses_harness: boolean, true if server-pattern harness is active
#}
{{ header }}
package {{ java_group_id }}.e2e;

{% if imports %}
{% for imp in imports %}
{{ imp }}
{% endfor %}
{% endif %}
{%- if uses_harness %}
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import java.io.IOException;
import java.net.Socket;
import java.net.URI;
import java.time.Instant;
{%- endif %}

/** E2e tests for category: {{ category }}. */
public class {{ test_class_name }} {
{%- if needs_object_mapper %}

    private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new Jdk8Module()).setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategies.SNAKE_CASE);
{%- endif %}
{%- if uses_harness %}

    private static Process harnessProcess;
    private static String sutUrl;

    @BeforeAll
    static void startHarness() throws Exception {
        String preset = System.getenv("SUT_URL");
        if (preset != null && !preset.isEmpty()) {
            sutUrl = preset;
            return;
        }

        String javaHome = System.getProperty("java.home");
        ProcessBuilder pb = new ProcessBuilder(
            javaHome + "/bin/java",
            "-cp", System.getProperty("java.class.path"),
            "{{ java_group_id }}.e2e.HarnessMain"
        );
        // Inherit parent process environment so native library paths are available
        pb.environment().putAll(System.getenv());
        pb.redirectErrorStream(false);
        harnessProcess = pb.start();

        // Drain harness output in background (optional port discovery, but primarily
        // to prevent pipe blocking). The harness may emit HARNESS_PORT but stdout buffering
        // when piped often prevents timely delivery, so we don't rely on it — instead we
        // use a default port and TCP polling below.
        java.io.BufferedReader reader = new java.io.BufferedReader(
            new java.io.InputStreamReader(harnessProcess.getInputStream(), "UTF-8")
        );
        java.io.BufferedReader errReader = new java.io.BufferedReader(
            new java.io.InputStreamReader(harnessProcess.getErrorStream(), "UTF-8")
        );
        Thread stderrDrainer = new Thread(() -> {
            try {
                String errLine;
                while ((errLine = errReader.readLine()) != null) {
                    System.err.println("[Harness stderr] " + errLine);
                }
            } catch (java.io.IOException ignored) {}
        });
        stderrDrainer.setDaemon(true);
        stderrDrainer.start();
        Thread stdoutDrainer = new Thread(() -> {
            try {
                String outLine;
                while ((outLine = reader.readLine()) != null) {
                    // Optionally parse HARNESS_PORT if present, but don't rely on it
                    if (outLine.startsWith("HARNESS_PORT=")) {
                        System.out.println("[Harness port marker] " + outLine);
                    }
                }
            } catch (java.io.IOException ignored) {}
        });
        stdoutDrainer.setDaemon(true);
        stdoutDrainer.start();

        // Use the default harness port and TCP polling to verify reachability
        int harnessPort = 8000;
        String host = "127.0.0.1";
        int port = harnessPort;
        sutUrl = "http://" + host + ":" + port;

        // TCP-readiness probe: poll until the harness accepts connections on the configured port
        long deadline = System.currentTimeMillis() + 15_000;
        boolean ready = false;
        while (System.currentTimeMillis() < deadline) {
            if (harnessProcess.isAlive() == false) {
                break;
            }
            try (Socket socket = new Socket(host, port)) {
                ready = true;
                break;
            } catch (IOException e) {
                Thread.sleep(100);
            }
        }

        if (!ready) {
            if (harnessProcess != null) {
                harnessProcess.destroyForcibly();
            }
            throw new RuntimeException("Harness did not become reachable at " + sutUrl + " within 15s");
        }

        System.setProperty("SUT_URL", sutUrl);
    }

    @AfterAll
    static void stopHarness() throws Exception {
        if (harnessProcess != null && harnessProcess.isAlive()) {
            harnessProcess.destroy();
            if (!harnessProcess.waitFor(5, java.util.concurrent.TimeUnit.SECONDS)) {
                harnessProcess.destroyForcibly();
            }
        }
    }
{%- endif %}

{{ fixtures_body }}
}
