package com.atlassian.bitbucket.mesh.sidecar;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * A {@link CommandLineRunner} which blocks reading from {@code stdin} and terminates the JVM when {@code stdin} is
 * closed, for use when running as a sidecar process on Bitbucket Server.
 */
public class SidecarRunner implements CommandLineRunner, ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(SidecarRunner.class);

    private final SidecarChannel channel;

    private ApplicationContext applicationContext;

    public SidecarRunner(SidecarChannel channel) {
        this.channel = channel;
    }

    @Override
    public void run(String... args) throws IOException {
        //Spring invokes runners after the ApplicationContext starts, so by this point we're ready to accept
        //requests. Writing "Ready" notifies Bitbucket Server we're up
        channel.send("Ready");

        //Read from stdin until it's closed
        handleStdin();

        //When stdin closes, that's the signal to shutdown
        shutdown();
    }

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    private void handleStdin() throws IOException {
        log.debug("Waiting for stdin to close");

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if ("ping".equalsIgnoreCase(line)) {
                    channel.send("pong");
                } else {
                    log.warn("Unexpected stdin: {}", line);
                }
            }
        }
    }

    private void shutdown() {
        log.info("Shutting down");

        //Shutting down the ApplicationContext is done on a separate thread so it can be abandoned if it takes
        //too long.
        Thread shutdown = new Thread(() -> SpringApplication.exit(applicationContext), "spring-shutdown");
        shutdown.setDaemon(true); //Daemon so it won't keep the JVM running
        shutdown.start();
        try {
            shutdown.join(TimeUnit.SECONDS.toMillis(30L));
            if (shutdown.isAlive()) {
                log.warn("The system failed to shutdown within 30s. The JVM will be abandoned");
            } else {
                log.info("Shutdown complete");
            }
        } catch (InterruptedException e) {
            log.warn("Interrupted while waiting for shutdown to complete. The JVM will be abandoned");
        }
    }
}
