/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * 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 com.alibaba.nacos.address.controller;

import com.alibaba.nacos.address.component.AddressServerGeneratorManager;
import com.alibaba.nacos.address.component.AddressServerManager;
import com.alibaba.nacos.address.constant.AddressServerConstants;
import com.alibaba.nacos.address.misc.Loggers;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.healthcheck.AbstractHealthChecker;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.common.utils.InternetAddressUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.naming.core.ClusterOperator;
import com.alibaba.nacos.naming.core.InstanceOperator;
import com.alibaba.nacos.naming.core.v2.ServiceManager;
import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata;
import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager;
import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Optional;

/**
 * Address server cluster controller.
 *
 * @author pbting
 * @since 1.1.0
 */
@RestController
@RequestMapping({AddressServerConstants.ADDRESS_SERVER_REQUEST_URL + "/nodes"})
public class AddressServerClusterController {
    
    private final InstanceOperator instanceOperator;
    
    private final NamingMetadataManager metadataManager;
    
    private final ClusterOperator clusterOperator;
    
    private final AddressServerManager addressServerManager;
    
    private final AddressServerGeneratorManager addressServerGeneratorManager;
    
    public AddressServerClusterController(InstanceOperator instanceOperator, NamingMetadataManager metadataManager,
            ClusterOperator clusterOperator, AddressServerManager addressServerManager,
            AddressServerGeneratorManager addressServerGeneratorManager) {
        this.instanceOperator = instanceOperator;
        this.metadataManager = metadataManager;
        this.clusterOperator = clusterOperator;
        this.addressServerManager = addressServerManager;
        this.addressServerGeneratorManager = addressServerGeneratorManager;
    }
    
    
    /**
     * Create new cluster.
     *
     * @param product Ip list of products to be associated
     * @param cluster Ip list of product cluster to be associated
     * @param ips     will post ip list.
     * @return result of create new cluster
     */
    @RequestMapping(value = "", method = RequestMethod.POST)
    public ResponseEntity<String> postCluster(@RequestParam(required = false) String product,
            @RequestParam(required = false) String cluster, @RequestParam(name = "ips") String ips) {
        
        //1. prepare the storage name for product and cluster
        String productName = addressServerGeneratorManager.generateProductName(product);
        String clusterName = addressServerManager.getDefaultClusterNameIfEmpty(cluster);
        
        //2. prepare the response name for product and cluster to client
        String rawProductName = addressServerManager.getRawProductName(product);
        String rawClusterName = addressServerManager.getRawClusterName(cluster);
        Loggers.ADDRESS_LOGGER.info("put cluster node,the cluster name is " + cluster + "; the product name=" + product
                + "; the ip list=" + ips);
        ResponseEntity<String> responseEntity;
        try {
            String serviceName = addressServerGeneratorManager.generateNacosServiceName(productName);
            
            Result result = registerCluster(serviceName, rawProductName, clusterName, ips);
            if (InternetAddressUtil.checkOk(result.getCheckResult())) {
                responseEntity = ResponseEntity
                        .ok("product=" + rawProductName + ",cluster=" + rawClusterName + "; put success with size="
                                + result.getSize());
            } else {
                responseEntity = ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result.getCheckResult());
            }
        } catch (Exception e) {
            responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
        
        return responseEntity;
    }
    
    private Result registerCluster(String serviceName, String productName, String clusterName, String ips)
            throws NacosException {
        String serviceWithoutGroup = NamingUtils.getServiceName(serviceName);
        String groupName = NamingUtils.getGroupName(serviceName);
        Service service = Service.newService(Constants.DEFAULT_NAMESPACE_ID, groupName, serviceWithoutGroup, false);
        service = ServiceManager.getInstance().getSingleton(service);
        if (service.isEphemeral()) {
            return new Result(
                    String.format("Service %s is ephemeral service, can't use as address server", serviceName), 0);
        }
        ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(new ServiceMetadata());
        if (!serviceMetadata.getClusters().containsKey(clusterName)) {
            ClusterMetadata metadata = new ClusterMetadata();
            metadata.setHealthyCheckType(AbstractHealthChecker.None.TYPE);
            metadata.setHealthChecker(new AbstractHealthChecker.None());
            clusterOperator.updateClusterMetadata(Constants.DEFAULT_NAMESPACE_ID, serviceName, clusterName, metadata);
        }
        String[] ipArray = addressServerManager.splitIps(ips);
        String checkResult = InternetAddressUtil.checkIps(ipArray);
        if (InternetAddressUtil.checkOk(checkResult)) {
            List<Instance> instanceList = addressServerGeneratorManager
                    .generateInstancesByIps(serviceName, productName, clusterName, ipArray);
            for (Instance instance : instanceList) {
                instanceOperator.registerInstance(Constants.DEFAULT_NAMESPACE_ID, serviceName, instance);
            }
        }
        return new Result(checkResult, ipArray.length);
    }
    
    /**
     * Delete cluster.
     *
     * @param product Ip list of products to be associated
     * @param cluster Ip list of product cluster to be associated
     * @param ips     will delete ips.
     * @return delete result (the cluster information is return if success, exception information is return if  fail)
     */
    @RequestMapping(value = "", method = RequestMethod.DELETE)
    public ResponseEntity<String> deleteCluster(@RequestParam(required = false) String product,
            @RequestParam(required = false) String cluster, @RequestParam String ips) {
        //1. prepare the storage name for product and cluster
        String productName = addressServerGeneratorManager.generateProductName(product);
        String clusterName = addressServerManager.getDefaultClusterNameIfEmpty(cluster);
        
        //2. prepare the response name for product and cluster to client
        String rawProductName = addressServerManager.getRawProductName(product);
        String rawClusterName = addressServerManager.getRawClusterName(cluster);
        ResponseEntity responseEntity = ResponseEntity.status(HttpStatus.OK)
                .body("product=" + rawProductName + ", cluster=" + rawClusterName + " delete success.");
        try {
            
            String serviceName = addressServerGeneratorManager.generateNacosServiceName(productName);
            String serviceWithoutGroup = NamingUtils.getServiceName(serviceName);
            String groupName = NamingUtils.getGroupName(serviceName);
            Optional<com.alibaba.nacos.naming.core.v2.pojo.Service> service = com.alibaba.nacos.naming.core.v2.ServiceManager
                    .getInstance().getSingletonIfExist(Constants.DEFAULT_NAMESPACE_ID, groupName, serviceWithoutGroup);
            
            if (!service.isPresent()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("product=" + rawProductName + " not found.");
            }
            if (StringUtils.isBlank(ips)) {
                // delete all ips from the cluster
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("ips must not be empty.");
            }
            // delete specified ip list
            String[] ipArray = addressServerManager.splitIps(ips);
            String checkResult = InternetAddressUtil.checkIps(ipArray);
            if (InternetAddressUtil.checkOk(checkResult)) {
                List<Instance> instanceList = addressServerGeneratorManager
                        .generateInstancesByIps(serviceName, rawProductName, clusterName, ipArray);
                for (Instance each : instanceList) {
                    instanceOperator.removeInstance(Constants.DEFAULT_NAMESPACE_ID, serviceName, each);
                }
            } else {
                responseEntity = ResponseEntity.status(HttpStatus.BAD_REQUEST).body(checkResult);
            }
        } catch (Exception e) {
            
            responseEntity = ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getCause());
        }
        
        return responseEntity;
    }
    
    private class Result {
        
        private final String checkResult;
        
        private final int size;
        
        public Result(String checkResult, int size) {
            this.checkResult = checkResult;
            this.size = size;
        }
        
        public String getCheckResult() {
            return checkResult;
        }
        
        public int getSize() {
            return size;
        }
    }
}
