package org.gcube.keycloak.oidc.avatar.importer.libravatar;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;

public class Libravatar {

    protected static final Logger logger = LoggerFactory.getLogger(Libravatar.class);

    private String email;
    private LibravatarOptions options = new LibravatarOptions().withoutHttps();

    private Libravatar(final String email) {
        Preconditions.checkNotNull(email).toLowerCase();
        this.email = email;
    }

    /**
     * Construct new libravatar downloader for the provided email
     * @param email the email address
     * @return the new libravatar downloader instance
     */
    public static Libravatar from(String email) {
        return new Libravatar(email);
    }

    /**
     * Adds he options to be used for download
     * @param options the libravatar options
     * @return
     */
    public Libravatar withOptions(LibravatarOptions options) {
        this.options = options;
        return this;
    }

    /***
     * Performs the avatar download by searching for a domain specific server and then on the libravatar public service
     * @return the avatar's byte array
     * @throws LibravatarException if no default has been set in options ({@link LibravatarOptions#defaultingTo(LibravatarDefaultImage)}) and the image hasn't found on remote server 
     */
    public byte[] downloadArray() throws LibravatarException {
        try (InputStream is = download()) {
            return IOUtils.toByteArray(is);
        } catch (IOException e) {
            throw new LibravatarException("Cannot download image byte's array for email: " + email, e);
        }
    }

    /***
     * Performs the avatar download by searching for a domain specific server and then on the libravatar public service
     * @return the avatar byte array
     * @throws LibravatarException if no default has been set in options ({@link LibravatarOptions#defaultingTo(LibravatarDefaultImage)}) and the image hasn't found on remote server
     * 
     */
    public InputStream download() throws LibravatarException {
        try {
            return getURL().openStream();
        } catch (IOException e) {
            throw new LibravatarException("Cannot open image stream for email: " + email, e);
        }
    }

    /**
     * Construct the URL to be used for the download
     * @return the URL to use
     * @throws MalformedURLException
     */
    private URL getURL() throws MalformedURLException {
        logger.debug("Constructing download URL for email: {}", email);
        logger.debug("Getting the DNS queries domain first");
        String baseTarget = options.isUseHttps() ? DNSUtil.resolveSecureSRVRecordFromEmail(email)
                : DNSUtil.resolveSRVRecordFromEmail(email);

        if (baseTarget != null) {
            logger.debug("Building URL with the DNS queried domain");
            return buildURL(baseTarget);
        } else {
            logger.debug("Defaulting to the liravatar URL");
            return buildURL(options.isUseHttps() ? LibravatarOptions.getSecureBaseUri() : LibravatarOptions.getBaseUri());
        }

    }

    /**
     * Builds the URL with query string to be used starting from the provided base URL
     * @param baseURL the base url to be used
     * @return the complete URL ready to be called
     * @throws MalformedURLException
     */
    private URL buildURL(String baseURL) throws MalformedURLException {
        StringBuilder sb = new StringBuilder(baseURL);
        String hash = options.isUseSHA256() ? DigestUtils.sha256Hex(email.getBytes())
                : DigestUtils.md5Hex(email.getBytes());

        logger.trace("Computed hash is: {}", hash);
        sb.append(hash);

        Map<String, String> parametersMap = options.toParametersMap();
        String queryString = parametersMap.entrySet().stream().map(p -> p.getKey() + "=" + p.getValue())
                .reduce((p1, p2) -> p1 + "&" + p2).orElse("");

        if (queryString != null && !"".equals(queryString)) {
            sb.append("?");
            sb.append(queryString);
        }

        String url = sb.toString();
        logger.trace("Resulting URL string is: {}", url);
        return new URL(url);
    }
}