/*
 * 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.shenyu.admin.listener;

import org.apache.shenyu.admin.config.properties.ClusterProperties;
import org.apache.shenyu.admin.mode.cluster.service.ClusterSelectMasterService;
import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
import org.apache.shenyu.common.dto.AppAuthData;
import org.apache.shenyu.common.dto.DiscoverySyncData;
import org.apache.shenyu.common.dto.MetaData;
import org.apache.shenyu.common.dto.PluginData;
import org.apache.shenyu.common.dto.ProxySelectorData;
import org.apache.shenyu.common.dto.ProxyApiKeyData;
import org.apache.shenyu.common.dto.RuleData;
import org.apache.shenyu.common.dto.SelectorData;
import org.apache.shenyu.common.utils.JsonUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Event forwarders, which forward the changed events to each
 * ConfigEventListener.
 */
@Component
public class DataChangedEventDispatcher implements ApplicationListener<DataChangedEvent>, InitializingBean {
    
    private static final Logger LOG = LoggerFactory.getLogger(DataChangedEventDispatcher.class);
    
    private final ApplicationContext applicationContext;
    
    private List<DataChangedListener> listeners;
    
    @Resource
    private ClusterProperties clusterProperties;
    
    @Resource
    @Nullable
    private ClusterSelectMasterService shenyuClusterSelectMasterService;
    
    public DataChangedEventDispatcher(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void onApplicationEvent(@NotNull final DataChangedEvent event) {
        for (DataChangedListener listener : listeners) {
            if (!(listener instanceof AbstractDataChangedListener)
                    && clusterProperties.isEnabled()
                    && Objects.nonNull(shenyuClusterSelectMasterService)
                    && !shenyuClusterSelectMasterService.isMaster()) {
                LOG.info("received DataChangedEvent, not master, pass");
                return;
            }
            final int size = event.getSource() instanceof java.util.Collection ? ((java.util.Collection<?>) event.getSource()).size() : 1;
            LOG.info("received DataChangedEvent, group={}, size={}, type={}", event.getGroupKey(), size, event.getEventType());
            if (LOG.isDebugEnabled()) {
                LOG.debug("received DataChangedEvent payload: {}", JsonUtils.toJson(event));
            }
            switch (event.getGroupKey()) {
                case APP_AUTH:
                    listener.onAppAuthChanged((List<AppAuthData>) event.getSource(), event.getEventType());
                    break;
                case PLUGIN:
                    listener.onPluginChanged((List<PluginData>) event.getSource(), event.getEventType());
                    break;
                case RULE:
                    listener.onRuleChanged((List<RuleData>) event.getSource(), event.getEventType());
                    break;
                case SELECTOR:
                    listener.onSelectorChanged((List<SelectorData>) event.getSource(), event.getEventType());
                    break;
                case META_DATA:
                    listener.onMetaDataChanged((List<MetaData>) event.getSource(), event.getEventType());
                    break;
                case PROXY_SELECTOR:
                    listener.onProxySelectorChanged((List<ProxySelectorData>) event.getSource(), event.getEventType());
                    break;
                case AI_PROXY_API_KEY:
                    listener.onAiProxyApiKeyChanged((List<ProxyApiKeyData>) event.getSource(), event.getEventType());
                    break;
                case DISCOVER_UPSTREAM:
                    listener.onDiscoveryUpstreamChanged((List<DiscoverySyncData>) event.getSource(),
                            event.getEventType());
                    applicationContext.getBean(LoadServiceDocEntry.class).loadDocOnUpstreamChanged(
                            (List<DiscoverySyncData>) event.getSource(), event.getEventType());
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + event.getGroupKey());
            }
        }
    }
    
    @Override
    public void afterPropertiesSet() {
        Collection<DataChangedListener> listenerBeans = applicationContext.getBeansOfType(DataChangedListener.class)
                .values();
        this.listeners = Collections.unmodifiableList(new ArrayList<>(listenerBeans));
    }
}
