/**
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed 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 io.fabric8.elasticsearch.plugin.acl;

import java.util.function.UnaryOperator;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.threadpool.ThreadPool;

import io.fabric8.elasticsearch.plugin.ConfigurationSettings;
import io.fabric8.elasticsearch.plugin.OpenshiftRequestContextFactory.OpenshiftRequestContext;
import io.fabric8.elasticsearch.plugin.PluginSettings;
import io.fabric8.elasticsearch.plugin.kibana.KibanaSeed;
import io.fabric8.elasticsearch.plugin.rest.RestChannelInterceptor;
import io.fabric8.elasticsearch.util.RequestUtils;

/**
 * REST filter to update the ACL when a user first makes a request
 */
public class DynamicACLFilter implements ConfigurationSettings {

    private static final Logger LOGGER = Loggers.getLogger(DynamicACLFilter.class);
    private final String kibanaVersion;
    private final String kbnVersionHeader;
    private final String cdmProjectPrefix;
    private KibanaSeed kibanaSeed;
    private final ACLDocumentManager aclManager;
    private final RequestUtils utils;
    private final ThreadContext threadContext;
    private final String defaultKibanaIndex;

    public DynamicACLFilter(final PluginSettings settings, 
            final KibanaSeed seed, 
            final Client client, 
            final ThreadPool threadPool,
            final RequestUtils utils,
            final ACLDocumentManager aclManager) {
        this.threadContext = threadPool.getThreadContext();
        this.kibanaSeed = seed;
        this.kibanaVersion = settings.getKibanaVersion();
        this.kbnVersionHeader = settings.getKbnVersionHeader();
        this.cdmProjectPrefix = settings.getCdmProjectPrefix();
        this.defaultKibanaIndex = settings.getDefaultKibanaIndex();
        this.utils = utils;
        this.aclManager = aclManager;
    }

    /*
     * The hacky logic here is we assume authentication occurs using the authenticator
     * we provide.  This works first by allowing SG to use the authenticator to establish
     * a user and their roles.  We then dynamically update the ACL document, seed dashboards,
     * and modify the kibana uri if needed.   
     */
    public RestHandler wrap(final RestHandler original, final UnaryOperator<RestHandler> unaryOperator) {
        return new RestHandler() {
            
            private final RestHandler localHandler = new RestHandler() {
                
                @Override
                public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
                    if ((request = continueProcessing(request, channel)) != null) {
                        RestChannelInterceptor interceptor = new RestChannelInterceptor(channel, threadContext, defaultKibanaIndex);
                        original.handleRequest(request, interceptor, client);
                    }
                }
            };

            @Override
            public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
                
                RestHandler handler = unaryOperator.apply(localHandler);
                handler.handleRequest(request, channel, client);
            }
        };
    }

    public RestRequest continueProcessing(RestRequest request, RestChannel channel) throws Exception {
        try {
            if (threadContext.getTransient(OPENSHIFT_REQUEST_CONTEXT) != null) {
                OpenshiftRequestContext requestContext = threadContext.getTransient(OPENSHIFT_REQUEST_CONTEXT);
                request = utils.modifyRequest(request, requestContext, channel);
                Boolean syncAndSeed = threadContext.getTransient(SYNC_AND_SEED);
                if (requestContext != OpenshiftRequestContext.EMPTY){
                    if(Boolean.TRUE.equals(syncAndSeed)) {
                        LOGGER.debug("Seeding dashboards and syncing ACLs for user {}", requestContext.getUser());
                        utils.logRequest(request);
                        final String kbnVersion = getKibanaVersion(request);
                        kibanaSeed.setDashboards(requestContext, kbnVersion, cdmProjectPrefix);
                        aclManager.syncAcl(requestContext);
                    } else {
                        LOGGER.debug("Cache hit. Skipping dashboards and syncing ACLs for user {}", requestContext.getUser());
                    }
                }
            }
        } catch (Exception e) {
            LOGGER.error("Error handling request", e);
        }
        return request;
    }


    private String getKibanaVersion(final RestRequest request) {
        String kbnVersion = StringUtils.defaultIfEmpty(request.header(kbnVersionHeader), "");
        if (StringUtils.isEmpty(kbnVersion)) {
            return this.kibanaVersion;
        }
        return kbnVersion;
    }

}