
package eu.dnetlib.dhp.oa.zenodoapi;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpHeaders;
import org.apache.http.entity.ContentType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;

import eu.dnetlib.dhp.oa.zenodoapi.model.ZenodoModel;
import eu.dnetlib.dhp.oa.zenodoapi.model.ZenodoModelList;
import okhttp3.*;

public class ZenodoAPIClient implements Serializable {
	private static final Logger log = LoggerFactory.getLogger(ZenodoAPIClient.class);
	String urlString;
	String bucket;

	String deposition_id;
	String access_token;

	public static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");

	private static final MediaType MEDIA_TYPE_ZIP = MediaType.parse("application/zip");

	private static final MediaType MEDIA_TYPE_TAR = MediaType.parse("application/octet-stream");

	public String getUrlString() {
		return urlString;
	}

	public void setUrlString(String urlString) {
		this.urlString = urlString;
	}

	public String getBucket() {
		return bucket;
	}

	public void setBucket(String bucket) {
		this.bucket = bucket;
	}

	public void setDeposition_id(String deposition_id) {
		this.deposition_id = deposition_id;
	}

	public ZenodoAPIClient(String urlString, String access_token) {

		this.urlString = urlString;
		this.access_token = access_token;
	}

	/**
	 * Brand new deposition in Zenodo. It sets the deposition_id and the bucket where to store the files to upload
	 *
	 * @return response code
	 * @throws IOException
	 */
	public int newDeposition2() throws IOException {
		String json = "{}";

		URL url = new URL(urlString);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
		conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + access_token);
		conn.setRequestMethod("POST");
		conn.setDoOutput(true);
		try (OutputStream os = conn.getOutputStream()) {
			byte[] input = json.getBytes("utf-8");
			os.write(input, 0, input.length);
		}

		String body = getBody(conn);

		int responseCode = conn.getResponseCode();
		conn.disconnect();

		if (!checkOKStatus(responseCode))
			throw new IOException("Unexpected code " + responseCode + body);

		ZenodoModel newSubmission = new Gson().fromJson(body, ZenodoModel.class);
		this.bucket = newSubmission.getLinks().getBucket();
		this.deposition_id = newSubmission.getId();

		return responseCode;
	}

	public int newDeposition() throws IOException {
		String json = "{}";
		OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(600, TimeUnit.SECONDS).build();

		RequestBody body = RequestBody.create(json.getBytes());

		Request request = new Request.Builder()
			.url(urlString)
			.addHeader("Content-Type", "application/json") // add request headers
			.addHeader("Authorization", "Bearer " + access_token)
			.post(body)
			.build();

		try (Response response = httpClient.newCall(request).execute()) {

			if (!response.isSuccessful())
				throw new IOException("Unexpected code " + response + response.body().string());

			// Get response body
			json = response.body().string();

			ZenodoModel newSubmission = new Gson()
				.fromJson(json, ZenodoModel.class);
			this.bucket = newSubmission.getLinks().getBucket();
			this.deposition_id = newSubmission.getId();

			return response.code();

		}

	}

	/**
	 * Upload files in Zenodo.
	 *
	 * @param is the inputStream for the file to upload
	 * @param file_name the name of the file as it will appear on Zenodo
	 * @return the response code
	 */
	public int uploadIS(InputStream is, String file_name) throws IOException {

		URL url = new URL(bucket + "/" + file_name);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, "application/zip");
		conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + access_token);
		conn.setDoOutput(true);
		conn.setRequestMethod("PUT");

		byte[] buf = new byte[8192];
		int length;
		try (OutputStream os = conn.getOutputStream()) {
			while ((length = is.read(buf)) != -1) {
				os.write(buf, 0, length);
				os.flush();
			}

		}
		int responseCode = conn.getResponseCode();
		if (!checkOKStatus(responseCode)) {
			throw new IOException("Unexpected code " + responseCode + getBody(conn));
		}

		return responseCode;
	}

	@NotNull
	private String getBody(HttpURLConnection conn) throws IOException {
		String body = "{}";
		try (BufferedReader br = new BufferedReader(
			new InputStreamReader(conn.getInputStream(), "utf-8"))) {
			StringBuilder response = new StringBuilder();
			String responseLine = null;
			while ((responseLine = br.readLine()) != null) {
				response.append(responseLine.trim());
			}

			body = response.toString();

		}
		return body;
	}

	public int uploadIS3(InputStream is, String file_name, long len) throws IOException {
//		String urlString = "http://checkip.amazonaws.com/";
//		URL url = new URL(urlString);
//		try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
//			log.info(br.readLine());
//		}
		OkHttpClient httpClient = new OkHttpClient.Builder()
			.writeTimeout(600, TimeUnit.SECONDS)
			.readTimeout(600, TimeUnit.SECONDS)
			.connectTimeout(600, TimeUnit.SECONDS)
			.build();

		System.out.println(bucket + "/" + file_name);

		Request request = new Request.Builder()
			.url(bucket + "/" + file_name)
			.addHeader(HttpHeaders.CONTENT_TYPE, "application/json") // add request headers
			.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + access_token)
			.put(InputStreamRequestBody.create(MEDIA_TYPE_TAR, is, len))
			.build();

		log.info("URL: " + request.url().toString());
		// log.info("Headers: " + request.headers().toString());

		try (Response response = httpClient.newCall(request).execute()) {
			if (!response.isSuccessful())
				log.info("Unexpected code " + response + response.body().string());
			System.out.println("Unexpected code " + response + response.body().string());
			return response.code();
		}
	}

	/**
	 * Associates metadata information to the current deposition
	 *
	 * @param metadata the metadata
	 * @return response code
	 * @throws IOException
	 */
	public int sendMretadata(String metadata) throws IOException {

		URL url = new URL(urlString + "/" + deposition_id);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
		conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + access_token);
		conn.setDoOutput(true);
		conn.setRequestMethod("PUT");

		try (OutputStream os = conn.getOutputStream()) {
			byte[] input = metadata.getBytes("utf-8");
			os.write(input, 0, input.length);

		}

		final int responseCode = conn.getResponseCode();
		conn.disconnect();
		if (!checkOKStatus(responseCode))
			throw new IOException("Unexpected code " + responseCode + getBody(conn));

		return responseCode;

	}

	private boolean checkOKStatus(int responseCode) {

		if (HttpURLConnection.HTTP_OK == responseCode ||
			HttpURLConnection.HTTP_CREATED == responseCode)
			return true;
		return false;
	}

	/**
	 * To create a new version of an already published deposition. It sets the deposition_id and the bucket to be used
	 * for the new version.
	 *
	 * @param concept_rec_id the concept record id of the deposition for which to create a new version. It is the last
	 *            part of the url for the DOI Zenodo suggests to use to cite all versions: DOI: 10.xxx/zenodo.656930
	 *            concept_rec_id = 656930
	 * @return response code
	 * @throws IOException
	 * @throws MissingConceptDoiException
	 */
	public int newVersion2(String concept_rec_id) throws Exception, MissingConceptDoiException {
		setDepositionId(concept_rec_id, 1);
		String json = "{}";

		URL url = new URL(urlString + "/" + deposition_id + "/actions/newversion");
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + access_token);
		conn.setDoOutput(true);
		conn.setRequestMethod("POST");

		try (OutputStream os = conn.getOutputStream()) {
			byte[] input = json.getBytes("utf-8");
			os.write(input, 0, input.length);

		}

		String body = getBody(conn);

		int responseCode = conn.getResponseCode();

		conn.disconnect();
		if (!checkOKStatus(responseCode))
			throw new IOException("Unexpected code " + responseCode + body);

		ZenodoModel zenodoModel = new Gson().fromJson(body, ZenodoModel.class);
		String latest_draft = zenodoModel.getLinks().getLatest_draft();
		deposition_id = latest_draft.substring(latest_draft.lastIndexOf("/") + 1);
		bucket = getBucket(latest_draft);

		return responseCode;

	}

	public int newVersion(String concept_rec_id) throws Exception, MissingConceptDoiException {
		setDepositionId(concept_rec_id, 1);
		String json = "{}";

		OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(600, TimeUnit.SECONDS).build();

		RequestBody body = RequestBody.create(json, MEDIA_TYPE_JSON);

		Request request = new Request.Builder()
			.url(urlString + "/" + deposition_id + "/actions/newversion")
			.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + access_token)
			.post(body)
			.build();

		try (Response response = httpClient.newCall(request).execute()) {

			if (!response.isSuccessful()) {
				// log.info("response headers " + response.headers().toString());
				throw new IOException("Unexpected code " + response + response.body().string());
			}

			ZenodoModel zenodoModel = new Gson().fromJson(response.body().string(), ZenodoModel.class);
			String latest_draft = zenodoModel.getLinks().getLatest_draft();
			deposition_id = latest_draft.substring(latest_draft.lastIndexOf("/") + 1);
			bucket = getBucket(latest_draft);
			return response.code();

		}
	}

	/**
	 * To finish uploading a version or new deposition not published
	 * It sets the deposition_id and the bucket to be used
	 *
	 *
	 * @param deposition_id the deposition id of the not yet published upload
	 *            concept_rec_id = 656930
	 * @return response code
	 * @throws IOException
	 * @throws MissingConceptDoiException
	 */
	public int uploadOpenDeposition(String deposition_id) throws IOException, MissingConceptDoiException {
//		String urlamazon = "http://checkip.amazonaws.com/";
//		URL url = new URL(urlamazon);
//		try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
//			log.info(br.readLine());
//		}
		this.deposition_id = deposition_id;

		OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(600, TimeUnit.SECONDS).build();

		Request request = new Request.Builder()
			.url(urlString + "/" + deposition_id)
			.addHeader("Authorization", "Bearer " + access_token)
			.build();

		log.info("URL: " + request.url().toString());
		// log.info("Headers: " + request.headers().toString());

		try (Response response = httpClient.newCall(request).execute()) {

			if (!response.isSuccessful()) {
				log.info("Unexpected code " + response + response.body().string());
				throw new IOException("Unexpected code " + response + response.body().string());
			}
			ZenodoModel zenodoModel = new Gson()
				.fromJson(response.body().string(), ZenodoModel.class);
			bucket = zenodoModel.getLinks().getBucket();
			return response.code();

		}

	}

	private void setDepositionId(String concept_rec_id, Integer page) throws Exception, MissingConceptDoiException {
//		String urlString = "http://checkip.amazonaws.com/";
//		URL url = new URL(urlString);
//		try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
//			log.info(br.readLine());
//		}

		ZenodoModelList zenodoModelList = new Gson()
			.fromJson(getPrevDepositions(String.valueOf(page)), ZenodoModelList.class);

		for (ZenodoModel zm : zenodoModelList) {
			if (zm.getConceptrecid().equals(concept_rec_id)) {
				deposition_id = zm.getId();
				return;
			}
		}
		if (zenodoModelList.size() == 0)
			throw new MissingConceptDoiException(
				"The concept record id specified was missing in the list of depositions");
		setDepositionId(concept_rec_id, page + 1);

	}

	private String getPrevDepositions(String page) throws IOException {

		OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(600, TimeUnit.SECONDS).build();

		HttpUrl.Builder urlBuilder = HttpUrl
			.parse(urlString)// + "?access_token=" + access_token + "&page=" + page)
			.newBuilder();
		urlBuilder.addQueryParameter("page", page);
		String url = urlBuilder.build().toString();

		Request request = new Request.Builder()
			.url(url)
			.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()) // add request headers
			.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + access_token)
			.get()
			.build();

		log.info("URL: " + request.url().toString());
		log.info("Headers: " + request.headers().toString());

		try (Response response = httpClient.newCall(request).execute()) {

			if (!response.isSuccessful()) {
				log.info("response headers: " + response.headers());
				throw new IOException("Unexpected code " + response + response.body().string());
			}

			return response.body().string();

		}

	}

	private String getBucket(String inputUurl) throws IOException {

		URL url = new URL(inputUurl);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
		conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + access_token);
		conn.setDoOutput(true);
		conn.setRequestMethod("GET");

		String body = getBody(conn);

		int responseCode = conn.getResponseCode();

		conn.disconnect();
		if (!checkOKStatus(responseCode))
			throw new IOException("Unexpected code " + responseCode + body);

		ZenodoModel zenodoModel = new Gson().fromJson(body, ZenodoModel.class);

		return zenodoModel.getLinks().getBucket();

	}

}
