package org.testcontainers.utility;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.exception.InternalServerErrorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Component that responsible for container removal and automatic cleanup of dead containers at JVM shutdown.
 */
public final class ContainerReaper {

    private static final Logger LOGGER = LoggerFactory.getLogger(ContainerReaper.class);
    private static ContainerReaper instance;
    private final DockerClient dockerClient;
    private Map<String, String> registeredContainers = new ConcurrentHashMap<>();

    private ContainerReaper() {
        dockerClient = DockerClientFactory.instance().client();

        // If the JVM stops without containers being stopped, try and stop the container.
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            registeredContainers.forEach(this::stopContainer);
        }));
    }

    public synchronized static ContainerReaper instance() {
        if (instance == null) {
            instance = new ContainerReaper();
        }

        return instance;
    }

    /**
     * Register a container to be cleaned up, either on explicit call to stopAndRemoveContainer, or at JVM shutdown.
     * @param containerId the ID of the container
     * @param imageName the image name of the container (used for logging)
     */
    public void registerContainerForCleanup(String containerId, String imageName) {
        registeredContainers.put(containerId, imageName);
    }

    /**
     * Stop a potentially running container and remove it, including associated volumes.
     * @param containerId the ID of the container
     */
    public void stopAndRemoveContainer(String containerId) {
        stopContainer(containerId, registeredContainers.get(containerId));
    }

    /**
     * Stop a potentially running container and remove it, including associated volumes.
     * @param containerId the ID of the container
     * @param imageName the image name of the container (used for logging)
     */
    public void stopAndRemoveContainer(String containerId, String imageName) {
        stopContainer(containerId, imageName);

        registeredContainers.remove(containerId);
    }

    private void stopContainer(String containerId, String imageName) {
        try {
            LOGGER.trace("Stopping container: {}", containerId);
            dockerClient.killContainerCmd(containerId).exec();
            LOGGER.trace("Stopped container: {}", imageName);
        } catch (DockerException e) {
            LOGGER.trace("Error encountered shutting down container (ID: {}) - it may not have been stopped, or may already be stopped: {}", containerId, e.getMessage());
        }

        try {
            LOGGER.trace("Stopping container: {}", containerId);
            try {
                dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).exec();
                LOGGER.info("Removed container and associated volume(s): {}", imageName);
            } catch (InternalServerErrorException e) {
                LOGGER.trace("Exception when removing container with associated volume(s): {} (due to {})", imageName, e.getMessage());
            }
        } catch (DockerException e) {
            LOGGER.trace("Error encountered shutting down container (ID: {}) - it may not have been stopped, or may already be stopped: {}", containerId, e.getMessage());
        }
    }
}
@@ -77,16 +77,83 @@
 Client;%0A
+import com.github.dockerjava.api.command.InspectContainerResponse;%0A
 import c
@@ -273,24 +273,86 @@
 rException;%0A
+import com.github.dockerjava.api.exception.NotFoundException;%0A
 import org.s
@@ -2637,30 +2637,678 @@
 Name) %7B%0A
-        try %7B%0A
+%0A        boolean running;%0A        try %7B%0A            InspectContainerResponse containerInfo = dockerClient.inspectContainerCmd(containerId).exec();%0A            running = containerInfo.getState().getRunning();%0A        %7D catch (NotFoundException e) %7B%0A            LOGGER.trace(%22Was going to stop container but it apparently no longer exists: %7B%7D%22);%0A            return;%0A        %7D catch (DockerException e) %7B%0A            LOGGER.trace(%22Error encountered when checking container for shutdown (ID: %7B%7D) - it may not have been stopped, or may already be stopped: %7B%7D%22, containerId, e.getMessage());%0A            return;%0A        %7D%0A%0A        if (running) %7B%0A            try %7B%0A    
         
@@ -3356,32 +3356,36 @@
 , containerId);%0A
+    
             dock
@@ -3435,32 +3435,36 @@
 ();%0A            
+   
+ 
 LOGGER.trace(%22St
@@ -3497,32 +3497,36 @@
 eName);%0A        
+    
 %7D catch (DockerE
@@ -3531,32 +3531,36 @@
 rException e) %7B%0A
+    
             LOGG
@@ -3706,32 +3706,46 @@
 .getMessage());%0A
+            %7D%0A
         %7D%0A%0A     
@@ -3771,37 +3771,37 @@
   LOGGER.trace(%22
-Stopp
+Remov
 ing container: %7B

Make ContainerReaper more intelligent about not trying to kill stopped/missing containers