package it.com.atlassian.stash.installer.docker;

import com.atlassian.bitbucket.async.AsyncTestUtils;
import com.atlassian.bitbucket.async.WaitCondition;
import com.atlassian.bitbucket.server.ApplicationState;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import org.hamcrest.Description;
import org.junit.After;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

public class BaseDockerInstallerTest {
    private static final Logger log = LoggerFactory.getLogger(BaseDockerInstallerTest.class);

    protected static final String BITBUCKET_HOME = "/tmp/bitbucket-home";
    protected static final String BITBUCKET_INIT_SCRIPT = "/etc/init.d/atlbitbucket";
    protected static final String BITBUCKET_INSTALL_DIR = "/tmp/bitbucket-install";
    protected static final String BITBUCKET_INSTALLER_BINARY = "./bitbucket-installer-x64.bin";

    protected DockerContainer dockerContainer;

    @Before
    public void startContainer() {
        dockerContainer = DockerContainer.create("bitbucket-installer");
        dockerContainer.start();
    }

    @After
    public void stopContainer() {
        if (dockerContainer != null) {
            dockerContainer.stop();
        }
    }

    protected String bitbucketLog() {
        return sh("cat " + BITBUCKET_HOME + "/log/atlassian-bitbucket.log");
    }

    protected String runInstaller(String installerBinary, Map<String, String> varFile) {
        writeVarFile(varFile);
        sh("chmod a+x " + installerBinary);
        // Add "-Dinstall4j.debug=true" to get debug output in case something goes wrong
        String installerLog = sh(installerBinary + " -q -Dinstall4j.logToStderr=true -varfile varfile.properties");
        log.info("Installer log: \n{}", installerLog);
        return installerLog;
    }

    protected String sh(String command) {
        return dockerContainer.sh(command);
    }

    protected void waitForRun() {
        waitForStart(new WaitCondition() {
            public String lastState;

            @Override
            public boolean test() throws Exception {
                lastState = getState().orElse(null);
                return ApplicationState.RUNNING.name().equals(lastState);
            }

            @Override
            public void describeFailure(Description description) throws Exception {
                description.appendText("Expected startup to succeed, last state was '" + lastState + "', log\n" + bitbucketLog());
            }
        });
    }

    protected void waitForFirstRun() {
        waitForStart(new WaitCondition() {
            private Optional<String> lastState;

            @Override
            public boolean test() throws Exception {
                lastState = getState();
                return ApplicationState.FIRST_RUN.name().equals(lastState.orElse(null));
            }

            @Override
            public void describeFailure(Description description) throws Exception {
                description.appendText("Expected startup to succeed. Last state was '" + lastState + "', log:\n" + bitbucketLog());
            }
        });
    }

    protected void waitForElasticsearchSearchable() {
        waitForStart(new WaitCondition() {
            @Override
            public boolean test() throws Exception {
                return !"".equals(doBitbucketCodeSearch());
            }

            @Override
            public void describeFailure(Description description) throws Exception {
                description.appendText("Expected to be able to search using the Bitbucket rest endpoint but request failed");
            }
        });
    }

    protected String doBitbucketCodeSearch() {
        String searchComamnd = "curl --silent --fail --user admin:admin " +
                "'http://localhost:7991/rest/search/latest/search' " +
                "--header 'Content-Type: application/json' " +
                "--data '{\"query\":\"test\",\"entities\":{\"code\":{}},\"limits\":{\"primary\":10,\"secondary\":3}}'\n";

        return sh(searchComamnd).trim();
    }

    protected static void waitForStart(WaitCondition waitCondition) {
        AsyncTestUtils.waitFor(waitCondition, TimeUnit.SECONDS.toMillis(300), TimeUnit.SECONDS.toMillis(5));
    }

    private Optional<String> getState() {
        String json = sh("curl -s http://localhost:7991/status");
        JsonElement jsonElement = new JsonParser().parse(json);
        if (jsonElement.isJsonNull()) {
            return Optional.empty();
        }
        JsonElement stateElement = jsonElement.getAsJsonObject().get("state");
        return Optional.of(stateElement.getAsString());
    }

    private void writeVarFile(Map<String, String> vars) {
        StringBuilder contents = new StringBuilder();
        for (Map.Entry<String, String> var : vars.entrySet()) {
            contents.append(var.getKey()).append('=').append(var.getValue()).append("\\n");
        }
        sh("echo -e '" + contents.toString() + "' > /varfile.properties");
    }

}
