package com.atlassian.bitbucket.validation;

import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Set;

/**
 * Base class to validate the base URL of the HTTP and SSH protocols.
 */
public abstract class BaseUrlValidator<T extends Annotation> implements ConstraintValidator<T, String> {

    protected final boolean allowPath;
    protected final Set<String> schemes;

    protected BaseUrlValidator(boolean allowPath, String... schemes) {
        this.allowPath = allowPath;
        this.schemes = ImmutableSet.copyOf(schemes);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // follow the same pattern as org.hibernate.validator.constraints.impl.URLValidator: consider empty strings as valid
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        URI uri;
        try {
            uri = new URI(clean(value));
        } catch (URISyntaxException e) {
            return false;
        }

        return isValid(uri);
    }

    protected boolean isValid(URI uri) {

        // check the scheme matches the predefined ones
        if (!schemes.contains(uri.getScheme())) {
            return false;
        }

        // check the host is set
        if (StringUtils.isEmpty(uri.getHost())) {
            return false;
        }

        // disallow colon after host without port (STASHDEV-813)
        if (uri.getAuthority().endsWith(":")) {
            return false;
        }

        // prohibit any user info, query string and hash fragment
        for (String component : new String[]{uri.getUserInfo(), uri.getQuery(), uri.getFragment()}) {
            if (StringUtils.isNotEmpty(component)) {
                return false;
            }
        }

        // prohibit any path if requested
        if (!allowPath && StringUtils.isNotEmpty(uri.getPath())) {
            return false;
        }

        return true;
    }

    private String clean(String value) {
        int i = value.length() - 1;
        while (i > 0 && value.charAt(i) == '/') {
            i--;
        }
        return value.substring(0, i + 1);
    }

}
