package ru.r2cloud.predict;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.r2cloud.R2Cloud;
import ru.r2cloud.util.Util;

class OreKitDataClient {

	private static final Logger LOG = LoggerFactory.getLogger(OreKitDataClient.class);
	private static final int TIMEOUT = 10000;

	private final HttpClient httpclient;
	private final List<String> urls;

	public OreKitDataClient(List<String> urls) {
		if (urls == null || urls.isEmpty()) {
			throw new IllegalArgumentException("urls are blank. at least 1 is expected");
		}
		this.urls = urls;
		this.httpclient = HttpClient.newBuilder().version(Version.HTTP_2).followRedirects(Redirect.NORMAL).connectTimeout(Duration.ofMillis(TIMEOUT)).build();
	}

	public void downloadAndSaveTo(Path dst) throws IOException {
		IOException lastException = null;
		for (String cur : urls) {
			try {
				downloadAndSaveTo(cur, dst);
				return;
			} catch (IOException e) {
				LOG.info("unable to download from: {} error: {}", cur, e.getMessage());
				lastException = e;
			}
		}
		// safe check
		if (lastException != null) {
			throw lastException;
		}
	}

	private void downloadAndSaveTo(String url, Path dst) throws IOException {
		Path tempPath = dst.getParent().resolve(dst.getFileName() + ".tmp").normalize();
		if (Files.exists(tempPath) && !Util.deleteDirectory(tempPath)) {
			throw new RuntimeException("unable to delete tmp directory: " + tempPath);
		}
		Files.createDirectories(tempPath);
		Builder result = HttpRequest.newBuilder().uri(URI.create(url));
		result.timeout(Duration.ofMillis(TIMEOUT));
		result.header("User-Agent", R2Cloud.getVersion() + " [email protected]");
		HttpRequest request = result.build();
		try {
			HttpResponse<InputStream> response = httpclient.send(request, BodyHandlers.ofInputStream());
			if (response.statusCode() != 200) {
				throw new IOException("invalid status code: " + response.statusCode());
			}
			Optional<String> contentType = response.headers().firstValue("Content-Type");
			if (contentType.isEmpty() || !contentType.get().equals("application/zip")) {
				throw new IOException("Content-Type is empty or unsupported: " + contentType);
			}
			try (ZipInputStream zis = new ZipInputStream(response.body())) {
				ZipEntry zipEntry = null;
				while ((zipEntry = zis.getNextEntry()) != null) {
					Path destFile = tempPath.resolve(zipEntry.getName()).normalize();
					if (!destFile.startsWith(tempPath)) {
						throw new IOException("invalid archive. zip slip detected: " + destFile);
					}
					if (zipEntry.isDirectory()) {
						Files.createDirectories(destFile);
						continue;
					}
					if (!Files.exists(destFile.getParent())) {
						Files.createDirectories(destFile.getParent());
					}
					Files.copy(zis, destFile, StandardCopyOption.REPLACE_EXISTING);
				}

				Files.move(tempPath, dst, StandardCopyOption.REPLACE_EXISTING);
			}
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			throw new RuntimeException(e);
		}
	}

}