package aQute.maven.provider;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

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

import aQute.bnd.http.HttpClient;
import aQute.bnd.http.HttpRequestException;
import aQute.bnd.service.url.State;
import aQute.bnd.service.url.TaggedData;
import aQute.libg.cryptography.MD5;
import aQute.libg.cryptography.SHA1;
import aQute.maven.api.Program;
import aQute.maven.api.Revision;
import aQute.maven.provider.MetadataParser.ProgramMetadata;
import aQute.maven.provider.MetadataParser.RevisionMetadata;
import aQute.service.reporter.Reporter;

public class MavenRemoteRepository extends MavenBackingRepository {
	private final static Logger				logger				= LoggerFactory.getLogger(MavenRemoteRepository.class);
	final HttpClient						client;
	final Map<Revision,RevisionMetadata>	revisions			= new ConcurrentHashMap<>();
	final Map<Program,ProgramMetadata>		programs			= new ConcurrentHashMap<>();
	final String							base;
	final static long						DEFAULT_MAX_STALE	= TimeUnit.HOURS.toMillis(1);

	public MavenRemoteRepository(File root, HttpClient client, String base, Reporter reporter) throws Exception {
		super(root, base, reporter);
		this.client = client;
		this.base = base;
	}

	public TaggedData fetch(String path, File file) throws Exception {
		URL url = new URL(base + path);
		int n = 0;
		while (true)
			try {
				logger.debug("Fetching {}", path);

				TaggedData tag = client.build()
						.headers("User-Agent", "Bnd")
						.useCache(file, DEFAULT_MAX_STALE)
						.asTag()
						.go(url);
				logger.debug("Fetched {}", tag);
				if (tag.getState() == State.UPDATED) {

					// https://issues.sonatype.org/browse/NEXUS-4900

					if (!path.endsWith("/maven-metadata.xml")) {
						URL shaUrl = new URL(base + path + ".sha1");
						URL md5Url = new URL(base + path + ".md5");
						String sha = client.build().asString().timeout(15000).go(shaUrl);
						if (sha != null) {
							String fileSha = SHA1.digest(file).asHex();
							checkDigest(fileSha, sha, file);
						} else {
							String md5 = client.build().asString().timeout(15000).go(md5Url);
							if (md5 != null) {
								String fileMD5 = MD5.digest(file).asHex();
								checkDigest(fileMD5, md5, file);
							}
						}
					}
				}
				return tag;
			} catch (Exception e) {
				n++;
				if (n > 3)
					throw e;
				Thread.sleep(1000 * n);
			}
	}

	public void store(File file, String path) throws Exception {

		int n = 0;
		URL url = new URL(base + path);
		SHA1 sha1 = SHA1.digest(file);
		MD5 md5 = MD5.digest(file);

		try (TaggedData go = client.build().put().upload(file).updateTag().asTag().go(url);) {

			switch (go.getState()) {
				case NOT_FOUND :
				case OTHER :
					throw new IOException("Could not store " + path + " from " + file + " with " + go);

				case UNMODIFIED :
				case UPDATED :
				default :
					break;
			}
		}
		try (TaggedData tag = client.build().put().upload(sha1.asHex()).asTag().go(new URL(base + path + ".sha1"))) {}
		try (TaggedData tag = client.build().put().upload(md5.asHex()).asTag().go(new URL(base + path + ".md5"))) {}

	}

	public boolean delete(String path) throws Exception {
		URL url = new URL(base + path);
		TaggedData go = client.build().put().delete().get(TaggedData.class).go(url);
		if (go == null)
			return false;

		if (go.getResponseCode() == HttpURLConnection.HTTP_OK
				|| go.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT) {
			client.build().delete().async(new URL(base + path + ".sha1"));
			client.build().delete().async(new URL(base + path + ".md5"));
			return true;
		}

		throw new HttpRequestException(go);
	}

	public void close() {

	}

	@Override
	public String toString() {
		return "RemoteRepo [base=" + base + ", id=" + id + "]";
	}

	public String getUser() throws Exception {
		return client.getUserFor(base);
	}

	@Override
	public URI toURI(String remotePath) throws Exception {
		return new URI(base + remotePath);
	}

	public boolean isFile() {
		return false;
	}
}
