package inlein.client.tasks;

import inlein.client.*;
import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermission;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.*;

import javax.net.ssl.HttpsURLConnection;
import javax.xml.bind.DatatypeConverter;

public final class Upgrade extends Task {

    public static final Upgrade instance = new Upgrade();

    private static final String latestUrl = "https://github.com/hyPiRion/inlein/releases/latest";
    private static final String urlFormat = "https://github.com/hyPiRion/inlein/releases/download/%s/%s";
    private static final String clientFormatName = "inlein";
    private static final String localClientFormatName = "inlein-%s";
    private static final String daemonFormatName = "inlein-daemon-%s-standalone.jar";

    private Upgrade() {
        super("--upgrade",
              "Upgrades to the specified inlein version, or latest",
              "Upgrades to the specified inlein version, or latest if no argument is provided");
    }

    public void run(ServerConnection conn, String[] args) throws Exception {
        if (args.length >= 2) {
            System.out.printf("Upgrade expects 1 or 0 arguments, but got %d\n", args.length);
            System.exit(1);
        }
        if (Version.getVersion().endsWith("SNAPSHOT")) {
            System.out.println("Cannot upgrade snapshot versions!");
            System.exit(1);
        }
        String vsn = args.length == 0 ? latestVersion() : args[0];
        System.err.printf("Downloading version %s of inlein\n", vsn);
        String clientName = String.format(clientFormatName, vsn);
        String daemonName = String.format(daemonFormatName, vsn);
        String localName = String.format(localClientFormatName, vsn);
        Path clientPath = Paths.get(Utils.inleinHome(), "clients", localName);
        Path daemonPath = Paths.get(Utils.inleinHome(), "daemons", daemonName);
        Future<Void> clientDl = downloadSha256(String.format(urlFormat, vsn, clientName), clientPath);
        Future<Void> daemonDl = downloadSha256(String.format(urlFormat, vsn, daemonName), daemonPath);
        clientDl.get();
        // then override the jar currently running
        String inleinFile = System.getProperty("inlein.client.file");
        if (inleinFile == null) {
            System.out.println("Could not detect what the client file is named");
            System.exit(1);
        }
        Path inleinPath = Paths.get(inleinFile);
        while (Files.isSymbolicLink(inleinPath)) {
            inleinPath = Files.readSymbolicLink(inleinPath);
        }
        Set<PosixFilePermission> fperms = Files.getPosixFilePermissions(inleinPath);
        daemonDl.get();
        Files.copy(clientPath, inleinPath, StandardCopyOption.REPLACE_EXISTING);
        Files.setPosixFilePermissions(inleinPath, fperms);
        ShutdownDaemon.instance.run(conn, new String[0]);
        System.out.printf("Upgraded to %s of inlein.\n", vsn);
    }


    public static void downloadDaemon(String vsn) throws Exception {
        String daemonName = String.format(daemonFormatName, vsn);
        Path daemonPath = Paths.get(Utils.inleinHome(), "daemons", daemonName);
        downloadSha256(String.format(urlFormat, vsn, daemonName), daemonPath).get();
    }

    public static String latestVersion() throws Exception {
        URL url = new URL(latestUrl);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setInstanceFollowRedirects(false);

        if (conn.getResponseCode() != 302) {
            throw new Exception("Unable to detect latest inlein version");
        }
        String loc = conn.getHeaderField("location");
        return loc.substring(loc.lastIndexOf('/') + 1);
    }

    private static Future<Void> downloadSha256(String url, Path path) {
        FutureTask<Void> ft = new FutureTask<Void>(new ShaDownloader(url, path));
        new Thread(ft).start();
        return ft;
    }

    private static class ShaDownloader implements Callable<Void> {
        private final String url;
        private final Path path;

        ShaDownloader(String url, Path path) {
            this.url = url;
            this.path = path;
        }

        public Void call() throws Exception {
            boolean outdated = true;
            File f = path.toFile();
            if (f.exists()) {
                String actualSha = fileSha(f);
                String expectedSha = internetSha(url);
                outdated = ! expectedSha.equals(actualSha);
                // TODO: Emit warning message if actual != expected?
            }
            if (outdated) {
                Utils.downloadFile(url, path);
            }
            return null;
        }

        private static String fileSha(File f) throws Exception {
            try (InputStream is = new BufferedInputStream(new FileInputStream(f))) {
                MessageDigest digest = MessageDigest.getInstance("SHA-256");
                byte[] buf = new byte[16384];
                int len;
                while ((len = is.read(buf)) > 0) {
                    digest.update(buf, 0, len);
                }
                return DatatypeConverter.printHexBinary(digest.digest()).toLowerCase();
            }
        }

        private static String internetSha(String url) throws Exception {
            // I decided to make the .sha256-files compatible with sha256sum,
            // which is why the split is here.
            String upstreamFile = Utils.downloadFile(url + ".sha256");
            return upstreamFile.split(" ")[0];
        }
    }
}