/**
 * 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;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.http.HttpServerTransport.Dispatcher;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.NetworkPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestHandler;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportInterceptor;
import org.elasticsearch.watcher.ResourceWatcherService;

import com.floragunn.searchguard.SearchGuardPlugin;

import io.fabric8.elasticsearch.plugin.acl.ACLDocumentManager;
import io.fabric8.elasticsearch.plugin.acl.DynamicACLFilter;
import io.fabric8.elasticsearch.plugin.acl.SearchGuardSyncStrategyFactory;
import io.fabric8.elasticsearch.plugin.filter.FieldStatsResponseFilter;
import io.fabric8.elasticsearch.plugin.kibana.IndexMappingLoader;
import io.fabric8.elasticsearch.plugin.kibana.KibanaSeed;
import io.fabric8.elasticsearch.plugin.kibana.KibanaUtils;
import io.fabric8.elasticsearch.util.RequestUtils;

public class OpenShiftElasticSearchPlugin extends Plugin implements ConfigurationSettings, ActionPlugin, NetworkPlugin {

    private final Settings settings;
    private DynamicACLFilter aclFilter;
    private SearchGuardPlugin sgPlugin;

    public OpenShiftElasticSearchPlugin(final Settings settings) {
        this.settings = settings;
        this.sgPlugin = new SearchGuardPlugin(settings);
    }

    @Override
    public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool,
            ResourceWatcherService resourceWatcherService, ScriptService scriptService,
            NamedXContentRegistry namedXContentRegistry) {

        final PluginSettings pluginSettings = new PluginSettings(settings);
        final IndexMappingLoader indexMappingLoader = new IndexMappingLoader(settings);
        final PluginClient pluginClient = new PluginClient(client, threadPool.getThreadContext());
        final OpenshiftAPIService apiService = new OpenshiftAPIService();
        final RequestUtils requestUtils = new RequestUtils(pluginSettings, apiService);
        final OpenshiftRequestContextFactory contextFactory = new OpenshiftRequestContextFactory(settings, requestUtils,
                apiService, threadPool.getThreadContext());
        final SearchGuardSyncStrategyFactory documentFactory = new SearchGuardSyncStrategyFactory(pluginSettings);
        final KibanaUtils kUtils = new KibanaUtils(pluginSettings, pluginClient);
        final KibanaSeed seed = new KibanaSeed(pluginSettings, indexMappingLoader, pluginClient, kUtils);
        final ACLDocumentManager aclDocumentManager = new ACLDocumentManager(pluginClient, pluginSettings, documentFactory, threadPool);
        this.aclFilter = new DynamicACLFilter(pluginSettings, seed, client, threadPool, requestUtils, aclDocumentManager);
        
        PluginServiceFactory.setApiService(apiService);
        PluginServiceFactory.setContextFactory(contextFactory);
        PluginServiceFactory.setThreadContext(threadPool.getThreadContext());
        PluginServiceFactory.markReady();

        List<Object> list = new ArrayList<>();
        list.add(aclDocumentManager);
        list.add(pluginSettings);
        list.add(indexMappingLoader);
        list.add(pluginClient);
        list.add(requestUtils);
        list.add(apiService);
        list.add(contextFactory);
        list.add(documentFactory);
        list.add(kUtils);
        list.add(seed);
        list.add(aclFilter);
        list.add(new FieldStatsResponseFilter(pluginClient));
        list.addAll(sgPlugin.createComponents(client, clusterService, threadPool, resourceWatcherService, scriptService,
                namedXContentRegistry));
        return list;
    }

    @Override
    public UnaryOperator<RestHandler> getRestHandlerWrapper(final ThreadContext threadContext) {
        return (rh) -> aclFilter.wrap(rh, sgPlugin.getRestHandlerWrapper(threadContext));
    }
    
    @Override
    public List<RestHandler> getRestHandlers(Settings settings, RestController restController,
            ClusterSettings clusterSettings, IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
            IndexNameExpressionResolver indexNameExpressionResolver, Supplier<DiscoveryNodes> nodesInCluster) {
        List<RestHandler> list = new ArrayList<>();
        list.addAll(sgPlugin.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings,
                settingsFilter, indexNameExpressionResolver, nodesInCluster));
        return list;
    }

    @Override
    public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
        List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> list = new ArrayList<>();
        list.addAll(sgPlugin.getActions());
        return list;
    }

    @Override
    public void onIndexModule(IndexModule indexModule) {
        sgPlugin.onIndexModule(indexModule);
    }

    @Override
    public List<Class<? extends ActionFilter>> getActionFilters() {
        List<Class<? extends ActionFilter>> list = new ArrayList<>();
        list.add(FieldStatsResponseFilter.class);
        list.addAll(sgPlugin.getActionFilters());
        return list;
    }

    @Override
    public List<TransportInterceptor> getTransportInterceptors(ThreadContext threadContext) {
        List<TransportInterceptor> list = new ArrayList<>();
        list.addAll(sgPlugin.getTransportInterceptors(threadContext));
        return list;
    }

    @Override
    public Map<String, Supplier<Transport>> getTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays,
            CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry,
            NetworkService networkService) {
        Map<String, Supplier<Transport>> transports = sgPlugin.getTransports(settings, threadPool, bigArrays, circuitBreakerService, namedWriteableRegistry,
                networkService);
        return transports;
    }

    @Override
    public Map<String, Supplier<HttpServerTransport>> getHttpTransports(Settings settings, ThreadPool threadPool,
            BigArrays bigArrays, CircuitBreakerService circuitBreakerService,
            NamedWriteableRegistry namedWriteableRegistry, NamedXContentRegistry namedXContentRegistry,
            NetworkService networkService, Dispatcher dispatcher) {
        Map<String, Supplier<HttpServerTransport>> transports = sgPlugin.getHttpTransports(settings, threadPool, bigArrays, circuitBreakerService,
                namedWriteableRegistry, namedXContentRegistry, networkService, dispatcher);
        return transports;
    }

    @Override
    public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() {
        Collection<Class<? extends LifecycleComponent>> serviceClasses = sgPlugin.getGuiceServiceClasses();
        return serviceClasses;
    }

    @Override
    public Settings additionalSettings() {
        return Settings.builder()
                .put(sgPlugin.additionalSettings())
                .build();

    }

    @Override
    public List<Setting<?>> getSettings() {
        List<Setting<?>> settings = sgPlugin.getSettings();
        settings.add(Setting.intSetting(OPENSHIFT_CONTEXT_CACHE_MAXSIZE, DEFAULT_OPENSHIFT_CONTEXT_CACHE_MAXSIZE, Property.NodeScope));
        settings.add(Setting.longSetting(OPENSHIFT_CONTEXT_CACHE_EXPIRE_SECONDS, DEFAULT_OPENSHIFT_CONTEXT_CACHE_EXPIRE_SECONDS, 0, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_ES_KIBANA_SEED_MAPPINGS_APP, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_ES_KIBANA_SEED_MAPPINGS_OPERATIONS, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_ES_KIBANA_SEED_MAPPINGS_EMPTY, Property.NodeScope));
        settings.add(Setting.boolSetting(OPENSHIFT_CONFIG_COMMON_DATA_MODEL, true, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_CONFIG_PROJECT_INDEX_PREFIX, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_CONFIG_TIME_FIELD_NAME, Property.NodeScope));
        settings.add(Setting.simpleString(SG_CLIENT_KS_PATH, Property.NodeScope));
        settings.add(Setting.simpleString(SG_CLIENT_TS_PATH, Property.NodeScope));
        settings.add(Setting.boolSetting(OPENSHIFT_CONFIG_OPS_ALLOW_CLUSTER_READER, false, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_KIBANA_INDEX_MODE, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_ACL_ROLE_STRATEGY, Property.NodeScope));
        settings.add(Setting.listSetting(OPENSHIFT_KIBANA_OPS_INDEX_PATTERNS, Arrays.asList(DEFAULT_KIBANA_OPS_INDEX_PATTERNS),
                Function.identity(), Property.NodeScope, Property.Dynamic));
        settings.add(Setting.simpleString(OPENSHIFT_ACL_EXPIRE_IN_MILLIS, Property.NodeScope));
        settings.add(Setting.simpleString(KIBANA_CONFIG_INDEX_NAME, Property.NodeScope));
        settings.add(Setting.simpleString(KIBANA_CONFIG_VERSION, Property.NodeScope));
        settings.add(Setting.simpleString(KIBANA_VERSION_HEADER, Property.NodeScope));
        settings.add(Setting.simpleString(OPENSHIFT_ES_USER_PROFILE_PREFIX, Property.NodeScope));
        settings.add(Setting.listSetting(OPENSHIFT_CONFIG_OPS_PROJECTS,Arrays.asList(DEFAULT_OPENSHIFT_OPS_PROJECTS),
                Function.identity(), Property.NodeScope, Property.Dynamic));
        return settings;
    }

    @Override
    public List<String> getSettingsFilter() {
        List<String> filter = sgPlugin.getSettingsFilter();
        filter.add("openshift.*");
        filter.add("io.fabric8.elasticsearch.*");
        return filter;
    }

    @Override
    public void close() throws IOException {
        if (sgPlugin != null) {
            sgPlugin.close();
        }
        super.close();
    }

}