/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.brooklyn.entity.brooklynnode;

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

import java.net.URI;
import java.util.Map;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.stream.Streams;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;

public class EntityHttpClientImpl implements EntityHttpClient {
    private static final Logger LOG = LoggerFactory.getLogger(EntityHttpClientImpl.class);

    protected static interface HttpCall {
        public HttpToolResponse call(HttpClient client, URI uri);
    }

    protected Entity entity;
    protected AttributeSensor<?> urlSensor;
    protected ConfigKey<?> urlConfig;
    protected Predicate<Integer> responseSuccess = ResponseCodePredicates.success();

    protected EntityHttpClientImpl(Entity entity, AttributeSensor<?> urlSensor) {
        this.entity = entity;
        this.urlSensor = urlSensor;
    }

    protected EntityHttpClientImpl(Entity entity, ConfigKey<?> urlConfig) {
        this.entity = entity;
        this.urlConfig = urlConfig;
    }

    @Override
    public HttpTool.HttpClientBuilder getHttpClientForBrooklynNode() {
        String baseUrl = getEntityUrl();
        HttpTool.HttpClientBuilder builder = HttpTool.httpClientBuilder()
                .trustAll()
                .laxRedirect(true)
                .uri(baseUrl);
        if (entity.getConfig(BrooklynNode.MANAGEMENT_USER) != null) {
            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
                    entity.getConfig(BrooklynNode.MANAGEMENT_USER),
                    entity.getConfig(BrooklynNode.MANAGEMENT_PASSWORD));
            builder.credentials(credentials);
        }
        return builder;
    }

    @Override
    public EntityHttpClient responseSuccess(Predicate<Integer> responseSuccess) {
        this.responseSuccess = checkNotNull(responseSuccess, "responseSuccess");
        return this;
    }

    protected HttpToolResponse exec(String path, HttpCall httpCall) {
        HttpClient client = Preconditions.checkNotNull(getHttpClientForBrooklynNode(), "No address info for "+entity)
                .build();
        String baseUri = getEntityUrl();
        URI uri = URI.create(Urls.mergePaths(baseUri, path));

        HttpToolResponse result;
        try {
            result = httpCall.call(client, uri);
        } catch (Exception e) {
            Exceptions.propagateIfFatal(e);
            throw new IllegalStateException("Invalid response invoking " + uri + ": " + e, e);
        }
        Tasks.addTagDynamically(BrooklynTaskTags.tagForStream("http_response", Streams.byteArray(result.getContent())));
        if (!responseSuccess.apply(result.getResponseCode())) {
            LOG.warn("Invalid response invoking {}: response code {}\n{}: {}",
                    new Object[]{uri, result.getResponseCode(), result, new String(result.getContent())});
            throw new IllegalStateException("Invalid response invoking " + uri + ": response code " + result.getResponseCode());
        }
        return result;
    }

    @Override
    public HttpToolResponse get(String path) {
        return exec(path, new HttpCall() {
            @Override
            public HttpToolResponse call(HttpClient client, URI uri) {
                return HttpTool.httpGet(client, uri, MutableMap.<String, String>of());
            }
        });
    }

    @Override
    public HttpToolResponse post(String path, final Map<String, String> headers, final byte[] body) {
        return exec(path, new HttpCall() {
            @Override
            public HttpToolResponse call(HttpClient client, URI uri) {
                return HttpTool.httpPost(client, uri, headers, body);
            }
        });
    }

    @Override
    public HttpToolResponse post(String path, final Map<String, String> headers, final Map<String, String> formParams) {
        return exec(path, new HttpCall() {
            @Override
            public HttpToolResponse call(HttpClient client, URI uri) {
                return HttpTool.httpPost(client, uri, headers, formParams);
            }
        });
    }

    protected String getEntityUrl() {
        Preconditions.checkState(urlSensor == null ^ urlConfig == null, "Exactly one of urlSensor and urlConfig should be non-null for entity " + entity);
        Object url = null;
        if (urlSensor != null) {
            url = entity.getAttribute(urlSensor);
        } else if (urlConfig != null) {
            url = entity.getConfig(urlConfig);
        }
        Preconditions.checkNotNull(url, "URL sensor " + urlSensor + " for entity " + entity + " is empty");
        return url.toString();
    }

    @Override
    public HttpToolResponse delete(String path, final Map<String, String> headers) {
        return exec(path, new HttpCall() {
            @Override
            public HttpToolResponse call(HttpClient client, URI uri) {
                return HttpTool.httpDelete(client, uri, headers);
            }
        });
    }
}
