package com.atlassian.bitbucket.mesh.util;

import com.atlassian.bitbucket.mesh.credentials.Credentials;
import com.atlassian.bitbucket.mesh.credentials.CredentialsVisitor;
import com.atlassian.bitbucket.mesh.credentials.PrivateKeyCredentials;
import com.atlassian.bitbucket.mesh.credentials.UsernamePasswordCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.net.URI;
import java.net.URISyntaxException;

import static org.apache.commons.lang3.StringUtils.isEmpty;

public class UrlUtils {

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

    /**
     * Sets (adding or replacing as necessary) the {@code userInfo} component of the provided URL, if the provided
     * URL is valid. Otherwise, the URL is returned unchanged.
     * <p>
     * The {@code userInfo} to set is drawn from the provided {@link Credentials}, if any. If the credentials do
     * not include a username, the URL is returned unchanged.
     *
     * @param url         a valid URL
     * @param credentials the credentials to apply, from which the username will be drawn
     * @return the provided URL with its {@code userInfo} set or updated, the original URL if the provided URL was
     *         not valid, or {@code null} if the provided URL was {@code null}
     * @see #setUserInfo
     */
    @Nullable
    public static String maybeSetUserInfo(@Nullable String url, @Nullable Credentials credentials) {
        if (credentials == null) {
            return url;
        }

        return credentials.accept(new CredentialsVisitor<String>() {

            @Override
            public String visit(@Nonnull PrivateKeyCredentials credentials) {
                return maybeSetUserInfo(url, credentials.getUsername());
            }

            @Override
            public String visit(@Nonnull UsernamePasswordCredentials credentials) {
                return maybeSetUserInfo(url, credentials.getUsername());
            }
        });
    }

    /**
     * Sets (adding or replacing as necessary) the {@code userInfo} component of the provided URL, if the provided
     * URL is valid. Otherwise, the URL is returned unchanged.
     *
     * @param url      a valid URL
     * @param userInfo the userInfo to set
     * @return the provided URL with its {@code userInfo} set or updated, the original URL if the provided URL was
     *         not valid, or {@code null} if the provided URL was {@code null}
     * @see #setUserInfo
     */
    @Nullable
    public static String maybeSetUserInfo(@Nullable String url, @Nullable String userInfo) {
        try {
            return setUserInfo(url, userInfo);
        } catch (URISyntaxException e) {
            log.warn("Could not set user in URL [{}]", url, e);
            return url;
        }
    }

    /**
     * Sets (adding or replacing as necessary) the {@code userInfo} component of the provided URL.
     * <p>
     * See <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396 3.2.2</a> for the relevant spec.
     *
     * @param url      a valid URL
     * @param userInfo the userInfo to set
     * @return the provided URL with its {@code userInfo} set or updated, or {@code null} if the provided URL was
     *         {@code null}
     * @throws URISyntaxException if the supplied URL is not a valid URL
     */
    @Nullable
    public static String setUserInfo(@Nullable String url, @Nullable String userInfo) throws URISyntaxException {
        if (url == null || isEmpty(userInfo)) { //Also tests "" for userInfo because that's Protobuf's default
            return url;
        }

        URI uri = new URI(url);
        return new URI(uri.getScheme(), userInfo, uri.getHost(), uri.getPort(),
                uri.getPath(), uri.getQuery(), uri.getFragment()).toASCIIString();
    }
}
