/**
 * The Configurable allowing configurations to be set.
 */
public static final class Configurable {
    private static final ClientLogger LOGGER = new ClientLogger(Configurable.class);
    private static final String SDK_VERSION = "version";
    private static final Map<String, String> PROPERTIES
        = CoreUtils.getProperties("{{artifact-id}}.properties");

    private HttpClient httpClient;
    private HttpLogOptions httpLogOptions;
    private final List<HttpPipelinePolicy> policies = new ArrayList<>();
    private final List<String> scopes = new ArrayList<>();
    private RetryPolicy retryPolicy;
    private RetryOptions retryOptions;
    private Duration defaultPollInterval;

    private Configurable() {
    }

    /**
     * Sets the http client.
     *
     * @param httpClient the HTTP client.
     * @return the configurable object itself.
     */
    public Configurable withHttpClient(HttpClient httpClient) {
        this.httpClient = Objects.requireNonNull(httpClient, "'httpClient' cannot be null.");
        return this;
    }

    /**
     * Sets the logging options to the HTTP pipeline.
     *
     * @param httpLogOptions the HTTP log options.
     * @return the configurable object itself.
     */
    public Configurable withLogOptions(HttpLogOptions httpLogOptions) {
        this.httpLogOptions = Objects.requireNonNull(httpLogOptions, "'httpLogOptions' cannot be null.");
        return this;
    }

    /**
     * Adds the pipeline policy to the HTTP pipeline.
     *
     * @param policy the HTTP pipeline policy.
     * @return the configurable object itself.
     */
    public Configurable withPolicy(HttpPipelinePolicy policy) {
        this.policies.add(Objects.requireNonNull(policy, "'policy' cannot be null."));
        return this;
    }

    /**
     * Adds the scope to permission sets.
     *
     * @param scope the scope.
     * @return the configurable object itself.
     */
    public Configurable withScope(String scope) {
        this.scopes.add(Objects.requireNonNull(scope, "'scope' cannot be null."));
        return this;
    }

    /**
     * Sets the retry policy to the HTTP pipeline.
     *
     * @param retryPolicy the HTTP pipeline retry policy.
     * @return the configurable object itself.
     */
    public Configurable withRetryPolicy(RetryPolicy retryPolicy) {
        this.retryPolicy = Objects.requireNonNull(retryPolicy, "'retryPolicy' cannot be null.");
        return this;
    }

    /**
     * Sets the retry options for the HTTP pipeline retry policy.
     * <p>
     * This setting has no effect, if retry policy is set via {@link #withRetryPolicy(RetryPolicy)}.
     *
     * @param retryOptions the retry options for the HTTP pipeline retry policy.
     * @return the configurable object itself.
     */
    public Configurable withRetryOptions(RetryOptions retryOptions) {
        this.retryOptions = Objects.requireNonNull(retryOptions, "'retryOptions' cannot be null.");
        return this;
    }

    /**
     * Sets the default poll interval, used when service does not provide "Retry-After" header.
     *
     * @param defaultPollInterval the default poll interval.
     * @return the configurable object itself.
     */
    public Configurable withDefaultPollInterval(Duration defaultPollInterval) {
        this.defaultPollInterval = Objects.requireNonNull(defaultPollInterval, "'defaultPollInterval' cannot be null.");
        if (this.defaultPollInterval.isNegative()) {
            throw LOGGER.logExceptionAsError(new IllegalArgumentException("'defaultPollInterval' cannot be negative"));
        }
        return this;
    }

    /**
     * Creates an instance of {{service-name}} service API entry point.
     *
     * @param credential the credential to use.
     * @param profile the Azure profile for client.
     * @return the {{service-name}} service API instance.
     */
    public {{manager-class}} authenticate(TokenCredential credential, AzureProfile profile) {
        Objects.requireNonNull(credential, "'credential' cannot be null.");
        Objects.requireNonNull(profile, "'profile' cannot be null.");

        String clientVersion = PROPERTIES.getOrDefault(SDK_VERSION, "UnknownVersion");

        StringBuilder userAgentBuilder = new StringBuilder();
        userAgentBuilder.append("azsdk-java")
            .append("-")
            .append("{{package-name}}")
            .append("/")
            .append(clientVersion);
        if (!Configuration.getGlobalConfiguration().get("AZURE_TELEMETRY_DISABLED", false)) {
            userAgentBuilder.append(" (")
                .append(Configuration.getGlobalConfiguration().get("java.version"))
                .append("; ")
                .append(Configuration.getGlobalConfiguration().get("os.name"))
                .append("; ")
                .append(Configuration.getGlobalConfiguration().get("os.version"))
                .append("; auto-generated)");
        } else {
            userAgentBuilder.append(" (auto-generated)");
        }

        if (scopes.isEmpty()) {
            scopes.add(profile.getEnvironment().getManagementEndpoint() + "/.default");
        }
        if (retryPolicy == null) {
            if (retryOptions != null) {
                retryPolicy = new RetryPolicy(retryOptions);
            } else {
                retryPolicy = new RetryPolicy("Retry-After", ChronoUnit.SECONDS);
            }
        }
        List<HttpPipelinePolicy> policies = new ArrayList<>();
        policies.add(new UserAgentPolicy(userAgentBuilder.toString()));
        policies.add(new AddHeadersFromContextPolicy());
        policies.add(new RequestIdPolicy());
        policies.addAll(
            this.policies.stream()
                .filter(p -> p.getPipelinePosition() == HttpPipelinePosition.PER_CALL)
                .collect(Collectors.toList()));
        HttpPolicyProviders.addBeforeRetryPolicies(policies);
        policies.add(retryPolicy);
        policies.add(new AddDatePolicy());
        policies.add(new BearerTokenAuthenticationPolicy(credential, scopes.toArray(new String[0])));
        policies.addAll(
            this.policies.stream()
                .filter(p -> p.getPipelinePosition() == HttpPipelinePosition.PER_RETRY)
                .collect(Collectors.toList()));
        HttpPolicyProviders.addAfterRetryPolicies(policies);
        policies.add(new HttpLoggingPolicy(httpLogOptions));
        HttpPipeline httpPipeline = new HttpPipelineBuilder()
            .httpClient(httpClient)
            .policies(policies.toArray(new HttpPipelinePolicy[0])).build();
        return new {{manager-class}}(httpPipeline, profile, defaultPollInterval);
    }
}
