package org.zstack.simulator.storage.primary.nfs;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.zstack.core.thread.AsyncThread;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.rest.RESTFacade;
import org.zstack.kvm.KVMAgentCommands.AgentResponse;
import org.zstack.storage.primary.nfs.NfsPrimaryStorageKVMBackend;
import org.zstack.storage.primary.nfs.NfsPrimaryStorageKVMBackendCommands.*;
import org.zstack.storage.primary.nfs.NfsPrimaryToSftpBackupKVMBackend;
import org.zstack.simulator.AsyncRESTReplyer;
import org.zstack.simulator.kvm.VolumeSnapshotKvmSimulator;
import org.zstack.utils.Utils;
import org.zstack.utils.data.SizeUnit;
import org.zstack.utils.gson.JSONObjectUtil;
import org.zstack.utils.logging.CLogger;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Controller
public class NfsPrimaryStorageSimulator {
    CLogger logger = Utils.getLogger(NfsPrimaryStorageSimulator.class);
    
    @Autowired
    private RESTFacade restf;
    @Autowired
    private NfsPrimaryStorageSimulatorConfig config;
    @Autowired
    private VolumeSnapshotKvmSimulator volumeSnapshotKvmSimulator;

    private AsyncRESTReplyer replyer = new AsyncRESTReplyer();

    class Capacity {
        long total;
        long avail;
    }

    private void reply(HttpEntity<String> entity, NfsPrimaryStorageAgentResponse rsp) {
        replyer.reply(entity, rsp);
    }

    private Map<String, Capacity> capacityMap = new HashMap<String, Capacity>();

    private void setCapacity(NfsPrimaryStorageAgentCommand cmd, NfsPrimaryStorageAgentResponse rsp) {
        rsp.setTotalCapacity(config.totalCapacity);
        rsp.setAvailableCapacity(config.availableCapacity);
    }

    @AsyncThread
    private void doGetCapacity(HttpEntity<String> entity) {
        GetCapacityCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetCapacityCmd.class);
        GetCapacityResponse rsp = new GetCapacityResponse();
        setCapacity(cmd, rsp);
        reply(entity, rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.UNMOUNT_PRIMARY_STORAGE_PATH, method=RequestMethod.POST)
    public @ResponseBody String nfsUnmount(@RequestBody String body) {
        if (config.unmountException) {
            throw new CloudRuntimeException("unmount exception on purpose");
        }

        UnmountCmd cmd = JSONObjectUtil.toObject(body, UnmountCmd.class);
        AgentResponse rsp = new AgentResponse();
        if (config.unmountSuccess) {
            rsp.setSuccess(true);
            config.unmountCmds.add(cmd);
            logger.debug(String.format("Unmount %s", cmd.getMountPath()));
        } else {
            rsp.setSuccess(false);
            rsp.setError("Fail umount on purpose");
        }
        return JSONObjectUtil.toJsonString(rsp);
    }

    @AsyncThread
    private void doCreateRootVolumeFromTemplate(HttpEntity<String> entity) throws InterruptedException {
        CreateRootVolumeFromTemplateCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateRootVolumeFromTemplateCmd.class);
        CreateRootVolumeFromTemplateResponse rsp = new CreateRootVolumeFromTemplateResponse();
        if (!config.createRootVolumeFromTemplateSuccess) {
            rsp.setSuccess(false);
            rsp.setError("Fail create on purpose");
        } else {
            logger.debug(String.format("Created root volume from cached template %s to %s", cmd.getTemplatePathInCache(), cmd.getInstallUrl()));
        }
        
        reply(entity, rsp);
    }


    @RequestMapping(value=NfsPrimaryStorageKVMBackend.MOUNT_PRIMARY_STORAGE_PATH, method=RequestMethod.POST)
    public @ResponseBody String nfsMount(@RequestBody String body) {
        if (config.mountException) {
            throw new CloudRuntimeException("mount exception on purpose");
        }

        MountCmd cmd = JSONObjectUtil.toObject(body, MountCmd.class);
        MountAgentResponse rsp = new MountAgentResponse();
        if (config.mountSuccess) {
            Capacity cap = new Capacity();
            cap.total = config.totalCapacity;
            cap.avail = config.availableCapacity;
            capacityMap.put(cmd.getUuid(), cap);

            rsp.setTotalCapacity(cap.total);
            rsp.setAvailableCapacity(cap.avail);

            config.mountCmds.add(cmd);

            logger.debug(String.format("mount %s to %s", cmd.getUrl(), cmd.getMountPath()));
        } else {
            rsp.setSuccess(false);
            rsp.setError("Fail mount on purpose");
        }
        return JSONObjectUtil.toJsonString(rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.SYNC_GET_CAPACITY_PATH, method=RequestMethod.POST)
    private @ResponseBody String syncGetCapacity(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        GetCapacityCmd cmd = JSONObjectUtil.toObject(entity.getBody(), GetCapacityCmd.class);
        GetCapacityResponse rsp = new GetCapacityResponse();
        setCapacity(cmd, rsp);
        return JSONObjectUtil.toJsonString(rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.GET_CAPACITY_PATH, method=RequestMethod.POST)
    private @ResponseBody String getCapacity(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        doGetCapacity(entity);
        return null;
    }
    
    @RequestMapping(value=NfsPrimaryToSftpBackupKVMBackend.CREATE_VOLUME_FROM_TEMPLATE_PATH, method=RequestMethod.POST)
    private @ResponseBody String createRootVolumeFromTemplate(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        if (config.createRootVolumeFromTemplateException) {
            throw new CloudRuntimeException("Fail download on purpose");
        } else {
            doCreateRootVolumeFromTemplate(entity);
        }
        return null;
    }

    @RequestMapping(value=NfsPrimaryToSftpBackupKVMBackend.DOWNLOAD_FROM_SFTP_PATH, method=RequestMethod.POST)
    private @ResponseBody String downloadFromSftp(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        downloadFromSftp(entity);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.DELETE_PATH, method=RequestMethod.POST)
    private @ResponseBody String delete(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        DeleteCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DeleteCmd.class);
        DeleteResponse rsp = new DeleteResponse();
        if (!config.deleteSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            // we don't know if this is deleting snapshot
            // volumeSnapshotKvmSimulator will ignore the call if it's not
            volumeSnapshotKvmSimulator.delete(cmd.getInstallPath());
            config.deleteCmds.add(cmd);
        }

        reply(entity, rsp);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.MOVE_BITS_PATH, method=RequestMethod.POST)
    private @ResponseBody String move(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        move(entity);
        return null;
    }

    @AsyncThread
    private void move(HttpEntity<String> entity) {
        MoveBitsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), MoveBitsCmd.class);
        MoveBitsRsp rsp = new MoveBitsRsp();
        if (!config.moveBitsSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            config.moveBitsCmds.add(cmd);
        }

        reply(entity, rsp);
    }

    @AsyncThread
    private void downloadFromSftp(HttpEntity<String> entity) {
        DownloadBitsFromSftpBackupStorageCmd cmd = JSONObjectUtil.toObject(entity.getBody(), DownloadBitsFromSftpBackupStorageCmd.class);
        DownloadBitsFromSftpBackupStorageResponse rsp = new DownloadBitsFromSftpBackupStorageResponse();
        if (!config.downloadFromSftpSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            logger.debug(entity.getBody());
            config.downloadFromSftpCmds.add(cmd);
        }

        reply(entity, rsp);
    }


    @RequestMapping(value=NfsPrimaryToSftpBackupKVMBackend.UPLOAD_TO_SFTP_PATH, method=RequestMethod.POST)
    private @ResponseBody String copyToSftp(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        copyToSftp(entity);
        return null;
    }

    @AsyncThread
    private void copyToSftp(HttpEntity<String> entity) {
        UploadToSftpCmd cmd = JSONObjectUtil.toObject(entity.getBody(), UploadToSftpCmd.class);
        UploadToSftpResponse rsp = new UploadToSftpResponse();
        if (!config.uploadToSftp || cmd.getPrimaryStorageInstallPath().equals(config.backupSnapshotFailurePrimaryStorageInstallPath)) {
            rsp.setError("no purpose");
            rsp.setSuccess(false);
        } else {
            config.uploadToSftpCmds.add(cmd);
        }

        reply(entity, rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.OFFLINE_SNAPSHOT_MERGE, method=RequestMethod.POST)
    private @ResponseBody String offlineMergeSnapshot(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        OfflineMergeSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), OfflineMergeSnapshotCmd.class);
        OfflineMergeSnapshotRsp rsp = new OfflineMergeSnapshotRsp();
        if (!config.offlineMergeSnapshotSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            volumeSnapshotKvmSimulator.merge(cmd.getSrcPath(), cmd.getDestPath(), cmd.isFullRebase());
            config.offlineMergeSnapshotCmds.add(cmd);
        }

        reply(entity, rsp);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.CHECK_BITS_PATH, method=RequestMethod.POST)
    private @ResponseBody String checkBits(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        checkBits(entity);
        return null;
    }

    @AsyncThread
    private void checkBits(HttpEntity<String> entity) {
        CheckIsBitsExistingCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CheckIsBitsExistingCmd.class);
        CheckIsBitsExistingRsp rsp = new CheckIsBitsExistingRsp();
        if (config.checkImageSuccess) {
            rsp.setExisting(config.imageCache.contains(cmd.getInstallPath()));
        } else {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        }

        reply(entity, rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.CREATE_EMPTY_VOLUME_PATH, method=RequestMethod.POST)
    private @ResponseBody String createEmptyVolume(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        doCreateEmptyVolume(entity);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.CREATE_TEMPLATE_FROM_VOLUME_PATH, method=RequestMethod.POST)
    private @ResponseBody String createTemplateFromRootVolume(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        doCreateTemplateFromRootVolume(entity);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.REVERT_VOLUME_FROM_SNAPSHOT_PATH, method=RequestMethod.POST)
    private @ResponseBody String revertVolumeFromSnapshot(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        RevertVolumeFromSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RevertVolumeFromSnapshotCmd.class);
        RevertVolumeFromSnapshotResponse rsp = new RevertVolumeFromSnapshotResponse();
        if (config.revertVolumeFromSnapshotSuccess) {
            config.revertVolumeFromSnapshotCmds.add(cmd);
            rsp = volumeSnapshotKvmSimulator.revert(cmd);
        } else {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        }

        reply(entity, rsp);
        return null;
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.REBASE_MERGE_SNAPSHOT_PATH, method=RequestMethod.POST)
    private @ResponseBody String rebaseAndMergeSnapshot(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        rebaseAndMergeSnapshot(entity);
        return null;
    }

    @AsyncThread
    private void rebaseAndMergeSnapshot(HttpEntity<String> entity) {
        RebaseAndMergeSnapshotsCmd cmd = JSONObjectUtil.toObject(entity.getBody(), RebaseAndMergeSnapshotsCmd.class);
        RebaseAndMergeSnapshotsResponse rsp = new RebaseAndMergeSnapshotsResponse();
        if (!config.rebaseAndMergeSnapshotSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            rsp.setSize(SizeUnit.MEGABYTE.toByte(500));
            config.rebaseAndMergeSnapshotsCmds.add(cmd);
        }

        reply(entity, rsp);
    }

    @RequestMapping(value=NfsPrimaryStorageKVMBackend.MERGE_SNAPSHOT_PATH, method=RequestMethod.POST)
    private @ResponseBody String mergeSnapshot(HttpServletRequest req) throws InterruptedException {
        HttpEntity<String> entity = restf.httpServletRequestToHttpEntity(req);
        mergeSnapshot(entity);
        return null;
    }

    @AsyncThread
    private void mergeSnapshot(HttpEntity<String> entity) {
        MergeSnapshotCmd cmd = JSONObjectUtil.toObject(entity.getBody(), MergeSnapshotCmd.class);
        MergeSnapshotResponse rsp = new MergeSnapshotResponse();
        if (!config.mergeSnapshotSuccess) {
            rsp.setError("on purpose");
            rsp.setSuccess(false);
        } else {
            rsp.setSize(SizeUnit.MEGABYTE.toByte(500));
            config.mergeSnapshotCmds.add(cmd);
        }
        reply(entity, rsp);
    }

    @AsyncThread
    private void doCreateTemplateFromRootVolume(HttpEntity<String> entity) {
        CreateTemplateFromVolumeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateTemplateFromVolumeCmd.class);
        CreateTemplateFromVolumeRsp rsp = new CreateTemplateFromVolumeRsp();
        if (!config.createTemplateFromRootVolumeSuccess) {
            rsp.setSuccess(false);
            rsp.setError("Fail create template on purpose");
        } else {
            logger.debug(String.format("successfully create template from volume, %s", entity.getBody()));
        }
        reply(entity, rsp);
    }

    @AsyncThread
    private void doCreateEmptyVolume(HttpEntity<String> entity) {
        CreateEmptyVolumeCmd cmd = JSONObjectUtil.toObject(entity.getBody(), CreateEmptyVolumeCmd.class);
        CreateEmptyVolumeResponse rsp = new CreateEmptyVolumeResponse();
        if (!config.createEmptyVolumeSuccess) {
            rsp.setError("failed on purpose");
            rsp.setSuccess(false);
        } else {
            logger.debug(String.format("create empty volume[uuid:%s,  path:%s, size:%s]", cmd.getUuid(), cmd.getInstallUrl(), cmd.getSize()));
        }
        reply(entity, rsp);
    }
}
