package com.atlassian.stash.internal.avatar;

import com.atlassian.bitbucket.avatar.AvatarRequest;
import com.atlassian.bitbucket.user.Person;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriUtils;

import javax.annotation.Nonnull;
import java.io.UnsupportedEncodingException;
import java.util.Locale;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * An implementation of {@link AvatarSource} which uses <a href="http://www.gravatar.com">Gravatar</a> to provide
 * avatars based on e-mail addresses.
 */
@Component("urlAvatarSource")
public class UrlAvatarSource implements AvatarSource {

    private static final Logger log = LoggerFactory.getLogger(UrlAvatarSource.class);

    private final String defaultFallbackUrl;
    private final String httpUrlFormat;
    private final String httpsUrlFormat;
    private final MessageDigestPasswordEncoder md5;

    @Autowired
    public UrlAvatarSource(@Qualifier("md5Encoder") MessageDigestPasswordEncoder md5,
                           @Value("${avatar.url.format.http}") String httpUrlFormat,
                           @Value("${avatar.url.format.https}") String httpsUrlFormat,
                           @Value("${avatar.url.default}") String defaultFallbackUrl) {
        this.defaultFallbackUrl = defaultFallbackUrl;
        this.httpUrlFormat = httpUrlFormat;
        this.httpsUrlFormat = httpsUrlFormat;
        this.md5 = md5;
    }

    @Nonnull
    @Override
    public String getUrlForPerson(@Nonnull Person person, @Nonnull AvatarRequest request) {
        checkNotNull(person, "person");

        String emailAddress = person.getEmailAddress();
        return getUrlForPerson(emailAddress, request);
    }

    /**
     * Encodes the provided {@code url} for use as a query parameter, allowing it to be embedded in another URL.
     *
     * @param url the url to encode
     * @return the encoded URL
     */
    private String encodeUrl(String url) {
        try {
            return UriUtils.encodeQueryParam(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.warn("Failed to encode URL using UTF-8; embedding URL as-is", e);
            return url; //Try it anyway. It's most likely not a problem
        }
    }

    /**
     * Uses either the {@link #httpUrlFormat standard URL format} or the {@link #httpsUrlFormat secure URL format},
     * depending on whether the {@link AvatarRequest#isSecure() request is secure}, and formats in:
     * <ol>
     *     <li>Lowercased and MD5-encoded {@code emailAddress}</li>
     *     <li>{@link AvatarRequest#getSize() Requested size}</li>
     *     <li>{@link #defaultFallbackUrl Fallback URL}</li>
     *     <li>Unmodified {@code emailAddress}</li>
     * </ol>
     *
     * @param emailAddress the e-mail address for the person whose avatar is being requested
     * @param request      describes the avatar which is being requested
     * @return a URL which will return an avatar for the provided e-mail address
     */
    private String getUrlForPerson(String emailAddress, AvatarRequest request) {
        checkNotNull(request, "request");

        // this hash ensures Gravatar fallsback to the default url
        String hash = "00000000000000000000000000000000";
        if (StringUtils.isBlank(emailAddress)) {
            emailAddress = "";
        } else {
            hash = hash(emailAddress);
        }
        String format = request.isSecure() ? httpsUrlFormat : httpUrlFormat;
        return String.format(format, hash, request.getSize(), encodeUrl(defaultFallbackUrl), emailAddress);
    }

    /**
     * Generates a hex-encoded hash of the provided {@code emailAddress}. The bytes for the e-mail
     * address are decoded using UTF-8 of else an UnsupportedEncodingException is thrown. This should
     * not occur as UTF-8 is a mandatory encoding for the Java platform.
     *
     * @param emailAddress the e-mail address to hash
     * @return a hex-encoded hash of the provided address
     */
    private String hash(String emailAddress) {
        return md5.encodePassword(emailAddress.toLowerCase(Locale.US), null);
    }
}
