// 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 com.cloud.storage.resource;

import java.util.List;

import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
import org.apache.cloudstack.storage.resource.SecondaryStorageResourceHandler;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import com.cloud.agent.api.Answer;
import com.cloud.agent.api.BackupSnapshotCommand;
import com.cloud.agent.api.Command;
import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand;
import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand;
import com.cloud.agent.api.CreateVolumeFromSnapshotCommand;
import com.cloud.agent.api.storage.CopyVolumeCommand;
import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand;
import com.cloud.agent.api.storage.PrimaryStorageDownloadCommand;
import com.cloud.hypervisor.vmware.manager.VmwareHostService;
import com.cloud.hypervisor.vmware.manager.VmwareStorageManager;
import com.cloud.hypervisor.vmware.manager.VmwareStorageManagerImpl;
import com.cloud.hypervisor.vmware.manager.VmwareStorageMount;
import com.cloud.hypervisor.vmware.mo.ClusterMO;
import com.cloud.hypervisor.vmware.mo.DatastoreMO;
import com.cloud.hypervisor.vmware.mo.HostMO;
import com.cloud.hypervisor.vmware.mo.VmwareHostType;
import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHost;
import com.cloud.hypervisor.vmware.mo.VmwareHypervisorHostNetworkSummary;
import com.cloud.hypervisor.vmware.util.VmwareContext;
import com.cloud.hypervisor.vmware.util.VmwareHelper;
import com.cloud.serializer.GsonHelper;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.google.gson.Gson;
import com.vmware.vim25.ManagedObjectReference;
import org.apache.logging.log4j.ThreadContext;

public class VmwareSecondaryStorageResourceHandler implements SecondaryStorageResourceHandler, VmwareHostService, VmwareStorageMount {
    protected Logger logger = LogManager.getLogger(getClass());

    private final PremiumSecondaryStorageResource _resource;
    private final VmwareStorageManager _storageMgr;

    private final Gson _gson;
    private final StorageSubsystemCommandHandler storageSubsystemHandler;

    private static ThreadLocal<VmwareContext> currentContext = new ThreadLocal<VmwareContext>();

    /*
     * private Map<String, HostMO> _activeHosts = new HashMap<String, HostMO>();
     */

    public VmwareSecondaryStorageResourceHandler(PremiumSecondaryStorageResource resource, String nfsVersion) {
        _resource = resource;
        _storageMgr = new VmwareStorageManagerImpl(this, nfsVersion);
        _gson = GsonHelper.getGsonLogger();

        VmwareStorageProcessor storageProcessor = new VmwareStorageProcessor(this, true, this, resource.getTimeout(), null, null, _resource, nfsVersion);
        VmwareStorageSubsystemCommandHandler vmwareStorageSubsystemCommandHandler = new VmwareStorageSubsystemCommandHandler(storageProcessor, nfsVersion);
        vmwareStorageSubsystemCommandHandler.setStorageResource(_resource);
        vmwareStorageSubsystemCommandHandler.setStorageManager(_storageMgr);
        storageSubsystemHandler = vmwareStorageSubsystemCommandHandler;
    }

    private static String getCommandLogTitle(Command cmd) {
        StringBuffer sb = new StringBuffer();
        if (cmd.getContextParam("job") != null) {
            sb.append(cmd.getContextParam("job"));
        }

        sb.append(", cmd: ").append(cmd.getClass().getSimpleName());

        return sb.toString();
    }

    @Override
    public Answer executeRequest(Command cmd) {

        try {
            Answer answer;
            ThreadContext.push(getCommandLogTitle(cmd));
            logCommand(cmd);

            if (cmd instanceof PrimaryStorageDownloadCommand) {
                answer = execute((PrimaryStorageDownloadCommand)cmd);
            } else if (cmd instanceof BackupSnapshotCommand) {
                answer = execute((BackupSnapshotCommand)cmd);
            } else if (cmd instanceof CreatePrivateTemplateFromVolumeCommand) {
                answer = execute((CreatePrivateTemplateFromVolumeCommand)cmd);
            } else if (cmd instanceof CreatePrivateTemplateFromSnapshotCommand) {
                answer = execute((CreatePrivateTemplateFromSnapshotCommand)cmd);
            } else if (cmd instanceof CopyVolumeCommand) {
                answer = execute((CopyVolumeCommand)cmd);
            } else if (cmd instanceof CreateVolumeFromSnapshotCommand) {
                answer = execute((CreateVolumeFromSnapshotCommand)cmd);
            } else if (cmd instanceof StorageSubSystemCommand) {
                answer = storageSubsystemHandler.handleStorageCommands((StorageSubSystemCommand)cmd);
            } else if (cmd instanceof CreateEntityDownloadURLCommand) {
                answer = execute((CreateEntityDownloadURLCommand)cmd);
            } else {
                answer = _resource.defaultAction(cmd);
            }

            // special handling to pass-back context info for cleanups
            if (cmd.getContextParam("execid") != null) {
                answer.setContextParam("execid", cmd.getContextParam("execid"));
            }

            if (cmd.getContextParam("checkpoint") != null) {
                answer.setContextParam("checkpoint", cmd.getContextParam("checkpoint"));
            }

            if (cmd.getContextParam("checkpoint2") != null) {
                answer.setContextParam("checkpoint2", cmd.getContextParam("checkpoint2"));
            }

            if (logger.isDebugEnabled())
                logger.debug("Command execution answer: " + _gson.toJson(answer));

            return answer;
        } finally {
            if (logger.isDebugEnabled())
                logger.debug("Done executing " + _gson.toJson(cmd));
            recycleServiceContext();
            ThreadContext.pop();
        }
    }

    private void logCommand(Command cmd) {
        try {
            logger.debug(String.format("Executing command: [%s].", _gson.toJson(cmd)));
        } catch (Exception e) {
            logger.debug(String.format("Executing command: [%s].", cmd.getClass().getSimpleName()));
        }
    }

    protected Answer execute(CreateEntityDownloadURLCommand cmd) {
        boolean success = _storageMgr.execute(this, cmd);
        if (success) {
            return _resource.defaultAction(cmd);
        }
        return new Answer(cmd, false, "Failed to download");
    }

    private Answer execute(PrimaryStorageDownloadCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    private Answer execute(BackupSnapshotCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    private Answer execute(CreatePrivateTemplateFromVolumeCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    private Answer execute(CreatePrivateTemplateFromSnapshotCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    private Answer execute(CopyVolumeCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    private Answer execute(CreateVolumeFromSnapshotCommand cmd) {
        return _storageMgr.execute(this, cmd);
    }

    @Override
    public VmwareContext getServiceContext(Command cmd) {
        String guid = cmd.getContextParam("guid");
        if (guid == null || guid.isEmpty()) {
            logger.error("Invalid command context parameter guid");
            return null;
        }

        String username = cmd.getContextParam("username");
        if (username == null || username.isEmpty()) {
            logger.error("Invalid command context parameter username");
            return null;
        }

        String password = cmd.getContextParam("password");

        // validate command guid parameter
        String[] tokens = guid.split("@");
        if (tokens == null || tokens.length != 2) {
            logger.error("Invalid content in command context parameter guid");
            return null;
        }

        String vCenterAddress = tokens[1];
        String[] hostTokens = tokens[0].split(":");
        if (hostTokens == null || hostTokens.length != 2) {
            logger.error("Invalid content in command context parameter guid");
            return null;
        }

        int vCenterSessionTimeout = NumbersUtil.parseInt(cmd.getContextParam("vCenterSessionTimeout"), 1200000);

        try {
            _resource.ensureOutgoingRuleForAddress(vCenterAddress);

            VmwareContext context = currentContext.get();
            if (context != null && !context.validate()) {
                invalidateServiceContext(context);
                context = null;
            }
            if (context == null) {
                logger.info("Open new VmwareContext. vCenter: " + vCenterAddress + ", user: " + username + ", password: " + StringUtils.getMaskedPasswordForDisplay(password));
                VmwareSecondaryStorageContextFactory.setVcenterSessionTimeout(vCenterSessionTimeout);
                context = VmwareSecondaryStorageContextFactory.getContext(vCenterAddress, username, password);
            }
            if (context != null) {
                context.registerStockObject("serviceconsole", cmd.getContextParam("serviceconsole"));
                context.registerStockObject("manageportgroup", cmd.getContextParam("manageportgroup"));
                context.registerStockObject("noderuninfo", cmd.getContextParam("noderuninfo"));
            }
            currentContext.set(context);
            return context;
        } catch (Exception e) {
            logger.error("Unexpected exception " + e.toString(), e);
            return null;
        }
    }

    public void recycleServiceContext() {
        if (currentContext.get() != null) {
            VmwareContext context = currentContext.get();
            currentContext.set(null);
            assert (context.getPool() != null);
            context.getPool().registerContext(context);
        }
    }

    @Override
    public void invalidateServiceContext(VmwareContext context) {
        currentContext.set(null);
        VmwareSecondaryStorageContextFactory.invalidate(context);
    }

    @Override
    public VmwareHypervisorHost getHyperHost(VmwareContext context, Command cmd) {
        String guid = cmd.getContextParam("guid");
        assert (guid != null);

        String[] tokens = guid.split("@");
        assert (tokens != null && tokens.length == 2);

        ManagedObjectReference morHyperHost = new ManagedObjectReference();
        String[] hostTokens = tokens[0].split(":");
        if (hostTokens == null || hostTokens.length != 2) {
            logger.error("Invalid content in command context parameter guid");
            return null;
        }

        morHyperHost.setType(hostTokens[0]);
        morHyperHost.setValue(hostTokens[1]);

        if (morHyperHost.getType().equalsIgnoreCase("HostSystem")) {
            HostMO hostMo = new HostMO(context, morHyperHost);

            try {

                ManagedObjectReference mor = hostMo.getHyperHostCluster();
                ClusterMO clusterMo = new ClusterMO(hostMo.getContext(), mor);
                List<Pair<ManagedObjectReference, String>> hostsInCluster = clusterMo.getClusterHosts();
                for (Pair<ManagedObjectReference, String> hostPair : hostsInCluster) {
                    HostMO hostIteratorMo = new HostMO(hostMo.getContext(), hostPair.first());

                    VmwareHypervisorHostNetworkSummary netSummary =
                        hostIteratorMo.getHyperHostNetworkSummary(hostIteratorMo.getHostType() == VmwareHostType.ESXi ? cmd.getContextParam("manageportgroup")
                            : cmd.getContextParam("serviceconsole"));
                    _resource.ensureOutgoingRuleForAddress(netSummary.getHostIp());

                    logger.info("Setup firewall rule for host: " + netSummary.getHostIp());
                }
            } catch (Throwable e) {
                logger.warn("Unable to retrive host network information due to exception " + e.toString() + ", host: " + hostTokens[0] + "-" + hostTokens[1]);
            }

            return hostMo;
        }

        assert (false);
        return new ClusterMO(context, morHyperHost);
    }

    @Override
    public String getWorkerName(VmwareContext context, Command cmd, int workerSequence, DatastoreMO dsMo) {
        assert (cmd.getContextParam("worker") != null);
        assert (workerSequence < 2);

        if (workerSequence == 0)
            return cmd.getContextParam("worker");
        return cmd.getContextParam("worker2");
    }

    @Override
    public String getMountPoint(String storageUrl, String nfsVersion) {
        return _resource.getRootDir(storageUrl, nfsVersion);
    }

    @Override
    public String createLogMessageException(Throwable e, Command command) {
        String message = String.format("%s failed due to [%s].", command.getClass().getSimpleName(), VmwareHelper.getExceptionMessage(e));
        logger.error(message, e);

        return message;
    }

    @Override
    public VmwareStorageProcessor getStorageProcessor() {
        return null;
    }
}
