package io.cameleer.server.app.runtime;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.AccessMode;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Capability;
import com.github.dockerjava.api.model.Volume;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.testcontainers.DockerClientFactory;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Real-Docker IT for the cameleer-runtime-loader image. Closes the regression
 * window where /app/jars wasn't pre-created in the loader image — the
 * orchestrator's fresh named volume mounted as root:root 0755 and wget
 * (running as UID 1000) failed with "Permission denied" only at runtime.
 *
 * <p>Replicates the exact hardening shape from
 * {@link DockerRuntimeOrchestrator}'s {@code baseHardenedHostConfig()} +
 * loader-specific bind, against a real artifact server, and asserts the
 * loader writes the expected file.
 *
 * <p>Image source: the loader's Dockerfile lives in the cameleer-saas repo
 * (`docker/runtime-loader/`) and is built+pushed by saas CI. This IT pulls
 * the published `:latest` tag — running it locally requires a `docker login
 * gitea.siegeln.net` (CI runners are pre-authenticated). The IT acts as a
 * cross-repo contract test: cameleer-server's hardening expectations versus
 * the saas-published artifact.
 */
@Testcontainers
class LoaderHardeningIT {

    private static final String LOADER_IMAGE =
            "gitea.siegeln.net/cameleer/cameleer-runtime-loader:latest";

    private static final int ARTIFACT_BYTES = 1024;

    private DockerClient dockerClient;
    private Network network;
    private GenericContainer<?> fileServer;
    private GenericContainer<?> loader;
    private Path fixtureDir;
    private String volumeName;

    @BeforeEach
    void setUp() throws IOException {
        dockerClient = DockerClientFactory.instance().client();
        network = Network.newNetwork();

        fixtureDir = Files.createTempDirectory("loader-it-fixture-");
        Path payload = fixtureDir.resolve("artifact.jar");
        Files.write(payload, new byte[ARTIFACT_BYTES]);

        fileServer = new GenericContainer<>("nginx:alpine")
                .withNetwork(network)
                .withNetworkAliases("file-server")
                .withFileSystemBind(
                        fixtureDir.toAbsolutePath().toString(),
                        "/usr/share/nginx/html",
                        BindMode.READ_ONLY);
        fileServer.start();

        volumeName = "cameleer-loader-it-" + UUID.randomUUID().toString().substring(0, 8);
        dockerClient.createVolumeCmd().withName(volumeName).exec();
    }

    @AfterEach
    void tearDown() throws IOException {
        if (loader != null) {
            try { loader.stop(); } catch (Exception ignored) { }
        }
        if (volumeName != null) {
            try { dockerClient.removeVolumeCmd(volumeName).exec(); } catch (Exception ignored) { }
        }
        if (fileServer != null) fileServer.stop();
        if (network != null) network.close();
        if (fixtureDir != null) {
            Files.walk(fixtureDir)
                    .sorted((a, b) -> b.getNameCount() - a.getNameCount())
                    .forEach(p -> {
                        try { Files.deleteIfExists(p); } catch (IOException ignored) { }
                    });
        }
    }

    @Test
    void loaderWritesArtifactUnderHardenedContract() {
        // OneShotStartupCheckStrategy succeeds only when the container has
        // exited with status 0. Anything else (non-zero exit, timeout) throws
        // ContainerLaunchException — the assertion below is a belt-and-braces
        // explicit check on the resolved exit code.
        loader = new GenericContainer<>(LOADER_IMAGE)
                .withNetwork(network)
                .withEnv("ARTIFACT_URL", "http://file-server/artifact.jar")
                .withEnv("ARTIFACT_EXPECTED_SIZE", String.valueOf(ARTIFACT_BYTES))
                .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig()
                        .withCapDrop(Capability.values())
                        .withSecurityOpts(List.of("no-new-privileges:true", "apparmor=docker-default"))
                        .withReadonlyRootfs(true)
                        .withPidsLimit(512L)
                        .withTmpFs(Map.of("/tmp", "rw,nosuid,size=64m"))
                        .withBinds(new Bind(volumeName, new Volume("/app/jars"), AccessMode.rw)))
                .withStartupCheckStrategy(new OneShotStartupCheckStrategy()
                        .withTimeout(Duration.ofSeconds(60)));

        loader.start();

        Long exit = dockerClient.inspectContainerCmd(loader.getContainerId())
                .exec()
                .getState()
                .getExitCodeLong();
        assertThat(exit)
                .as("loader must exit 0 — non-zero indicates the hardening contract "
                        + "broke the artifact write (e.g. /app/jars not owned by loader UID 1000)")
                .isZero();
    }
}
