/*
 * 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.dubbo.admin.service.impl;

import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.admin.common.util.Constants;
import org.apache.dubbo.admin.common.util.ConvertUtil;
import org.apache.dubbo.admin.common.util.OverrideUtils;
import org.apache.dubbo.admin.common.util.YamlParser;
import org.apache.dubbo.admin.model.adapter.DynamicConfigDTO2OverrideDTOAdapter;
import org.apache.dubbo.admin.model.adapter.LoadBalance2OverrideAdapter;
import org.apache.dubbo.admin.model.adapter.WeightToOverrideAdapter;
import org.apache.dubbo.admin.model.domain.LoadBalance;
import org.apache.dubbo.admin.model.domain.Override;
import org.apache.dubbo.admin.model.domain.Weight;
import org.apache.dubbo.admin.model.dto.BalancingDTO;
import org.apache.dubbo.admin.model.dto.DynamicConfigDTO;
import org.apache.dubbo.admin.model.dto.WeightDTO;
import org.apache.dubbo.admin.model.store.OverrideConfig;
import org.apache.dubbo.admin.model.store.OverrideDTO;
import org.apache.dubbo.admin.service.OverrideService;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


@Component
public class OverrideServiceImpl extends AbstractService implements OverrideService {

    @java.lang.Override
    public void saveOverride(DynamicConfigDTO override) {
        String id = ConvertUtil.getIdFromDTO(override);
        String path = getPath(id);
        String exitConfig = dynamicConfiguration.getConfig(path);
        List<OverrideConfig> configs = new ArrayList<>();
        OverrideDTO existOverride = new DynamicConfigDTO2OverrideDTOAdapter(override);
        if (exitConfig != null) {
            existOverride = YamlParser.loadObject(exitConfig, OverrideDTO.class);
            if (existOverride.getConfigs() != null) {
                for (OverrideConfig overrideConfig : existOverride.getConfigs()) {
                    if (Constants.CONFIGS.contains(overrideConfig.getType())) {
                        configs.add(overrideConfig);
                    }
                }
            }
        }
        configs.addAll(override.getConfigs());
        existOverride.setEnabled(override.isEnabled());
        existOverride.setConfigs(configs);
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(existOverride));

        //for2.6
        if (StringUtils.isNotEmpty(override.getService())) {
            List<Override> result = convertDTOtoOldOverride(override);
            for (Override o : result) {
                registry.register(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

    @java.lang.Override
    public void updateOverride(DynamicConfigDTO update) {
        String id = ConvertUtil.getIdFromDTO(update);
        String path = getPath(id);
        String exitConfig = dynamicConfiguration.getConfig(path);
        if (exitConfig == null) {
            //throw exception
        }
        OverrideDTO overrideDTO = YamlParser.loadObject(exitConfig, OverrideDTO.class);
        DynamicConfigDTO old = OverrideUtils.createFromOverride(overrideDTO);
        List<OverrideConfig> configs = new ArrayList<>();
        if (overrideDTO.getConfigs() != null) {
            List<OverrideConfig> overrideConfigs = overrideDTO.getConfigs();
            for (OverrideConfig config : overrideConfigs) {
                if (Constants.CONFIGS.contains(config.getType())) {
                    configs.add(config);
                }
            }
        }
        configs.addAll(update.getConfigs());
        overrideDTO.setConfigs(configs);
        overrideDTO.setEnabled(update.isEnabled());
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));

        //for 2.6
        if (StringUtils.isNotEmpty(update.getService())) {
            List<Override> oldOverrides = convertDTOtoOldOverride(old);
            List<Override> updatedOverrides = convertDTOtoOldOverride(update);
            for (Override o : oldOverrides) {
                registry.unregister(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
            for (Override o : updatedOverrides) {
                registry.register(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

    @java.lang.Override
    public void deleteOverride(String id) {
        if (StringUtils.isEmpty(id)) {
            // throw exception
        }
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        if (config == null) {
            //throw exception
        }
        OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
        DynamicConfigDTO old = OverrideUtils.createFromOverride(overrideDTO);
        List<OverrideConfig> newConfigs = new ArrayList<>();
        if (overrideDTO.getConfigs() != null && overrideDTO.getConfigs().size() > 0) {
            for (OverrideConfig overrideConfig : overrideDTO.getConfigs()) {
                if (Constants.CONFIGS.contains(overrideConfig.getType())) {
                    newConfigs.add(overrideConfig);
                }
            }
            if (newConfigs.size() == 0) {
                dynamicConfiguration.deleteConfig(path);
            } else {
                overrideDTO.setConfigs(newConfigs);
                dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));
            }
        } else {
            dynamicConfiguration.deleteConfig(path);
        }

        //for 2.6
        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
            List<Override> overrides = convertDTOtoOldOverride(old);
            for (Override o : overrides) {
                registry.unregister(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

    @java.lang.Override
    public void enableOverride(String id) {
        if (StringUtils.isEmpty(id)) {
            //throw exception
        }
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        if (config == null) {
            //throw exception
        }
        OverrideDTO override = YamlParser.loadObject(config, OverrideDTO.class);
        DynamicConfigDTO old = OverrideUtils.createFromOverride(override);
        override.setEnabled(true);
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(override));

        //2.6
        if (Constants.SERVICE.equals(override.getScope())) {
            List<Override> overrides = convertDTOtoOldOverride(old);
            for (Override o : overrides) {
                o.setEnabled(false);
                registry.unregister(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
                o.setEnabled(true);
                registry.register(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

    @java.lang.Override
    public void disableOverride(String id) {
        if (StringUtils.isEmpty(id)) {
            //throw exception
        }
        String path = getPath(id);
        if (dynamicConfiguration.getConfig(path) == null) {
            //throw exception
        }
        String config = dynamicConfiguration.getConfig(path);
        OverrideDTO override = YamlParser.loadObject(config, OverrideDTO.class);
        DynamicConfigDTO old = OverrideUtils.createFromOverride(override);
        override.setEnabled(false);
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(override));

        //for 2.6
        if (Constants.SERVICE.equals(override.getScope())) {
            List<Override> overrides = convertDTOtoOldOverride(old);
            for (Override o : overrides) {
                o.setEnabled(true);
                registry.unregister(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
                o.setEnabled(false);
                registry.register(o.toUrl().addParameter(Constants.COMPATIBLE_CONFIG, true));
            }
        }
    }

    @java.lang.Override
    public DynamicConfigDTO findOverride(String id) {
        if (StringUtils.isEmpty(id)) {
            //throw exception
        }
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            DynamicConfigDTO dynamicConfigDTO = OverrideUtils.createFromOverride(overrideDTO);
            if (dynamicConfigDTO != null) {
                dynamicConfigDTO.setId(id);
                if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                    ConvertUtil.detachIdToService(id, dynamicConfigDTO);
                }
            }
            return dynamicConfigDTO;
        }
        return null;
    }

    @java.lang.Override
    public void saveWeight(WeightDTO weightDTO) {
        String id = ConvertUtil.getIdFromDTO(weightDTO);
        String scope = ConvertUtil.getScopeFromDTO(weightDTO);
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        OverrideConfig overrideConfig = OverrideUtils.weightDTOtoConfig(weightDTO);
        OverrideDTO overrideDTO = insertConfig(config, overrideConfig, id, scope, Constants.WEIGHT);
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));

        //for 2.6
        if (Constants.SERVICE.equals(scope)) {
            registerWeight(weightDTO);
        }

    }

    @java.lang.Override
    public void updateWeight(WeightDTO weightDTO) {
        String id = ConvertUtil.getIdFromDTO(weightDTO);
        String scope = ConvertUtil.getScopeFromDTO(weightDTO);
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        WeightDTO oldWeight = null;
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null && configs.size() > 0) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.WEIGHT.equals(overrideConfig.getType())) {
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            oldWeight = OverrideUtils.configtoWeightDTO(overrideConfig, scope, id);
                        }
                        int index = configs.indexOf(overrideConfig);
                        OverrideConfig newConfig = OverrideUtils.weightDTOtoConfig(weightDTO);
                        configs.set(index, newConfig);
                        break;
                    }
                }
                dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));
            } else {
                //throw exception
            }
        } else {
            //throw exception
        }


        //for 2.6
        if (oldWeight != null) {
            unregisterWeight(oldWeight);
            registerWeight(weightDTO);
        }

    }

    @java.lang.Override
    public void deleteWeight(String id) {
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        OverrideConfig oldConfig = null;
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.WEIGHT.equals(overrideConfig.getType())) {
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            oldConfig = overrideConfig;
                        }
                        configs.remove(overrideConfig);
                        break;
                    }
                }
                if (configs.size() == 0) {
                    dynamicConfiguration.deleteConfig(path);
                } else {
                    dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));
                }

            }

            //for 2.6
            if (oldConfig != null) {
                String key = overrideDTO.getKey();
                WeightDTO weightDTO = OverrideUtils.configtoWeightDTO(oldConfig, overrideDTO.getScope(), key);
                unregisterWeight(weightDTO);
            }
        }
    }

    @java.lang.Override
    public WeightDTO findWeight(String id) {
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.WEIGHT.equals(overrideConfig.getType())) {
                        WeightDTO weightDTO = OverrideUtils.configtoWeightDTO(overrideConfig, overrideDTO.getScope(), id);
                        weightDTO.setId(id);
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            ConvertUtil.detachIdToService(id, weightDTO);
                        }
                        return weightDTO;
                    }
                }
            }
        }
        return null;
    }

    @java.lang.Override
    public void saveBalance(BalancingDTO balancingDTO) {
        String id = ConvertUtil.getIdFromDTO(balancingDTO);
        String scope = ConvertUtil.getScopeFromDTO(balancingDTO);
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        OverrideConfig overrideConfig = OverrideUtils.balancingDTOtoConfig(balancingDTO);
        OverrideDTO overrideDTO = insertConfig(config, overrideConfig, id, scope, Constants.BALANCING);
        dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));

        //for 2.6

        if (Constants.SERVICE.equals(scope)) {
            registerBalancing(balancingDTO);
        }
    }

    @java.lang.Override
    public void updateBalance(BalancingDTO balancingDTO) {
        String id = ConvertUtil.getIdFromDTO(balancingDTO);
        String scope = ConvertUtil.getScopeFromDTO(balancingDTO);
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        BalancingDTO oldBalancing = null;
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null && configs.size() > 0) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.BALANCING.equals(overrideConfig.getType())) {
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            oldBalancing = OverrideUtils.configtoBalancingDTO(overrideConfig, Constants.SERVICE, overrideDTO.getKey());
                        }
                        int index = configs.indexOf(overrideConfig);
                        OverrideConfig newConfig = OverrideUtils.balancingDTOtoConfig(balancingDTO);
                        configs.set(index, newConfig);
                        break;
                    }
                }
                dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));
            } else {
                //throw exception
            }
        } else {
            //throw exception
        }

        //for 2.6
        if (oldBalancing != null) {
            unregisterBalancing(oldBalancing);
            registerBalancing(balancingDTO);
        }
    }

    @java.lang.Override
    public void deleteBalance(String id) {
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        OverrideConfig oldConfig = null;
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.BALANCING.equals(overrideConfig.getType())) {
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            oldConfig = overrideConfig;
                        }
                        configs.remove(overrideConfig);
                        break;
                    }
                }
                if (configs.size() == 0) {
                    dynamicConfiguration.deleteConfig(path);
                } else {
                    dynamicConfiguration.setConfig(path, YamlParser.dumpObject(overrideDTO));
                }
            }
            //for 2.6
            if (oldConfig != null) {
                String key = overrideDTO.getKey();
                BalancingDTO balancingDTO = OverrideUtils.configtoBalancingDTO(oldConfig, Constants.SERVICE, key);
                unregisterBalancing(balancingDTO);
            }
        }
    }

    @java.lang.Override
    public BalancingDTO findBalance(String id) {
        String path = getPath(id);
        String config = dynamicConfiguration.getConfig(path);
        if (config != null) {
            OverrideDTO overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null) {
                for (OverrideConfig overrideConfig : configs) {
                    if (Constants.BALANCING.equals(overrideConfig.getType())) {
                        BalancingDTO balancingDTO = OverrideUtils.configtoBalancingDTO(overrideConfig, overrideDTO.getScope(), id);
                        balancingDTO.setId(id);
                        if (Constants.SERVICE.equals(overrideDTO.getScope())) {
                            ConvertUtil.detachIdToService(id, balancingDTO);
                        }
                        return balancingDTO;
                    }
                }
            }
        }
        return null;
    }

    private OverrideDTO insertConfig(String config, OverrideConfig overrideConfig, String key, String scope, String configType) {
        OverrideDTO overrideDTO = null;
        if(config == null) {
            overrideDTO = new OverrideDTO();
            overrideDTO.setKey(key);
            overrideDTO.setScope(scope);
            List<OverrideConfig> configs = new ArrayList<>();
            configs.add(overrideConfig);
            overrideDTO.setConfigs(configs);
        } else {
            overrideDTO = YamlParser.loadObject(config, OverrideDTO.class);
            List<OverrideConfig> configs = overrideDTO.getConfigs();
            if (configs != null) {
                for (OverrideConfig o : configs) {
                    if (configType.equals(o.getType())) {
                        configs.remove(o);
                        break;
                    }
                }
                configs.add(overrideConfig);
            } else {
                configs = new ArrayList<>();
                configs.add(overrideConfig);
            }
            overrideDTO.setConfigs(configs);
        }
        return overrideDTO;
    }

    private void overrideDTOToParams(Override override, OverrideConfig config) {
        Map<String, Object> parameters = config.getParameters();
        StringBuilder params = new StringBuilder();

        if (parameters != null) {
            for (Map.Entry<String, Object> entry : parameters.entrySet()) {
                String value = entry.getKey() + "=" + entry.getValue();
                params.append(value).append("&");
            }
        }
        if (StringUtils.isNotEmpty(params)) {
            int length = params.length();
            if (params.charAt(length - 1) == '&') {
                params.deleteCharAt(length - 1);
            }
        }
        override.setParams(params.toString());
    }
    private List<Override> convertDTOtoOldOverride(DynamicConfigDTO overrideDTO) {
        List<Override> result = new ArrayList<>();
        List<OverrideConfig> configs = overrideDTO.getConfigs();
        for (OverrideConfig config : configs) {
            if (Constants.CONFIGS.contains(config.getType())) {
                continue;
            }
            List<String> apps = config.getApplications();
            List<String> addresses = config.getAddresses();
            for (String address : addresses) {
                if (apps != null && apps.size() > 0) {
                    for (String app : apps) {
                        Override override = new Override();
                        override.setService(overrideDTO.getService());
                        override.setAddress(address);
                        override.setEnabled(overrideDTO.isEnabled());
                        overrideDTOToParams(override, config);
                        override.setApplication(app);
                        result.add(override);
                    }
                } else {
                    Override override = new Override();
                    override.setService(overrideDTO.getService());
                    override.setAddress(address);
                    override.setEnabled(overrideDTO.isEnabled());
                    overrideDTOToParams(override, config);
                    result.add(override);
                }
            }
        }
        return result;
    }

    private String getPath(String key) {
        key = key.replace("/", "*");
        return key + Constants.CONFIGURATOR_RULE_SUFFIX;
    }

    private void unregisterWeight(WeightDTO weightDTO) {
        List<String> addresses = weightDTO.getAddresses();
        if (addresses != null) {
            Weight weight = new Weight();
            weight.setService(weightDTO.getService());
            weight.setWeight(weightDTO.getWeight());
            for (String address : addresses) {
                weight.setAddress(address);
                Override override = new WeightToOverrideAdapter(weight);
                registry.unregister(override.toUrl());
            }
        }
    }

    private void registerWeight(WeightDTO weightDTO) {
        List<String> addresses = weightDTO.getAddresses();
        if (addresses != null) {
            Weight weight = new Weight();
            weight.setService(weightDTO.getService());
            weight.setWeight(weightDTO.getWeight());
            for (String address : addresses) {
                weight.setAddress(address);
                Override override = new WeightToOverrideAdapter(weight);
                registry.register(override.toUrl());
            }
        }
    }

    private void unregisterBalancing(BalancingDTO balancingDTO) {
        LoadBalance loadBalance = new LoadBalance();
        loadBalance.setService(balancingDTO.getService());
        loadBalance.setMethod(balancingDTO.getMethodName());
        loadBalance.setStrategy(balancingDTO.getStrategy());
        registry.unregister(new LoadBalance2OverrideAdapter(loadBalance).toUrl());
    }

    private void registerBalancing(BalancingDTO balancingDTO) {
        LoadBalance loadBalance = new LoadBalance();
        loadBalance.setService(balancingDTO.getService());
        loadBalance.setMethod(balancingDTO.getMethodName());
        loadBalance.setStrategy(balancingDTO.getStrategy());
        registry.register(new LoadBalance2OverrideAdapter(loadBalance).toUrl());
    }

}
