package com.bocloud.ims.service.installation;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bocloud.cmp.entity.enums.installation.ImageServiceUrl;
import com.bocloud.common.enums.BoCloudMethod;
import com.bocloud.common.enums.PostDataFormat;
import com.bocloud.common.http.HttpClient;
import com.bocloud.common.model.BocloudResult;
import com.bocloud.common.model.RequestContext;
import com.bocloud.common.model.Result;
import com.bocloud.common.utils.Common;
import com.bocloud.ims.common.config.StorageConfig;
import com.bocloud.ims.common.config.installation.ImageServiceConfig;
import com.bocloud.ims.common.utils.constant.Constant;
import com.bocloud.ims.entity.installation.YumRepo;
import com.bocloud.ims.entity.resource.Image;
import com.bocloud.ims.interfaces.installation.YumRepoService;
import com.bocloud.ims.interfaces.resource.ImageService;
import com.bocloud.ims.repository.resource.ImageRepository;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 镜像服务接口实现类
 * <p>
 * cop的image不用加锁，cmp加分布式锁即可
 *
 * @author shaolong_tan
 * @version 1.0
 * @since 2017.11.7
 */
@Service("installationimageService")
public class ImageServiceImpl extends com.bocloud.ims.service.resource.ImageServiceImpl implements ImageService {

	private static Logger logger = LoggerFactory.getLogger(ImageServiceImpl.class);

	private static String IMAGE_STATUS = "ACTIVE";
	@Autowired
	private ImageRepository imageRepository;
	@Autowired
	private ImageServiceConfig imageServiceConfig;
	@Autowired
	private StorageConfig storageConfig;
	@Autowired
	private YumRepoService yumRepoService;
	@Value("${upload.storage.path:/home/cmp}")
	private String nfs;

	@Override
	public BocloudResult create(Image image, RequestContext context) {
		try {
			if (null == image.getName()) {
				logger.warn("image name must provide at request");
				return new BocloudResult(false, "添加镜像失败：镜像名为空");
			}
			if (null == image.getOsType()) {
				logger.warn("image type must provide at request");
				return new BocloudResult(false, "添加镜像失败：镜像类型为空");
			}
			if (null == image.getOsVersion()) {
				logger.warn("image version must provide at request");
				return new BocloudResult(false, "添加镜像失败：镜像版本为空");
			}
			if (null == image.getFilePath()) {
				logger.warn("image file path must provide at request");
				return new BocloudResult(false, "添加镜像失败：镜像路径为空");
			}
			// check whether or not the image type and version have existed at
			// image service
			if (imageRepository.exists(image.getOsType(), image.getOsVersion())) {
				logger.warn("image {} 已经存在", image);
				return new BocloudResult(false, "添加镜像失败：镜像已经存在");
			}
			image.setIsoName(new File(image.getFilePath()).getName());
			image.setCreatorId(context.getTarget());
			// add the image to image service
			Map<String, String> paramMap = Maps.newHashMap();
			paramMap.put(Common.NAME, image.getOsType());
			paramMap.put("version", image.getOsVersion());
			paramMap.put("isolocal", imageServiceConfig.getNfs() + image.getFilePath());
			// request image-service to upload image
			Result result = request(null, buildUrl(ImageServiceUrl.IMAGES, paramMap), BoCloudMethod.PUT);
			JSONObject jsonResult = JSONObject.parseObject(result.getMessage(), JSONObject.class);
			String message = null;
			if (result.isFailed()) {
				message = jsonResult.getString("message");
			} else {
				// 获取image的大小
				File file = new File(storageConfig.getPath() + image.getFilePath());
				if (file.exists()) {
					DecimalFormat df = new DecimalFormat("0.00");
					String size = df.format((double) file.length() / 1024 / 1024);
					image.setSize(Double.parseDouble(size));
				} else {
					logger.warn("cop的nfs配置upload.storage.path={}不正确", storageConfig.getPath());
				}
				image.setCreatorId(context.getTarget());
				image.setStatus(IMAGE_STATUS);
				image.setCatalog("PM");
				imageRepository.save(image);
				message = "添加镜像成功";
			}
			return new BocloudResult(result.isSuccess(), image, message);
		} catch (Exception e) {
			logger.error("upload image fail:", e);
			return new BocloudResult(false, "上传物理机镜像失败");
		}
	}

	@Override
	public BocloudResult patch(Long id, JSONObject params, RequestContext context, String action) {
		try {
			if (!"sync".equalsIgnoreCase(action)) {
				return new BocloudResult(false, "装机部分image操作不支持:" + action);
			}
			// Get all image information and check whether or not they are valid yet.
			List<Image> dbImages = imageRepository.list();
			dbImages.removeIf(image -> !"PM".equals(image.getCatalog()));
			for (Image image : dbImages) {
				// get the image information from image-service
				Map<String, String> paramMap = Maps.newHashMap();
				paramMap.put(Common.NAME, image.getOsType());
				paramMap.put("version", image.getOsVersion());
				Result getResult = request(null, buildUrl(ImageServiceUrl.IMAGES, paramMap), BoCloudMethod.GET);
				if (getResult.isFailed()) {
					logger.warn("请求images失败:{},该image不可用，请求cmp删除该image", JSON.toJSON(getResult));
					// image service exception, delete it from cmp
					imageRepository.deletePhysicalImage(image.getOsType(), image.getOsVersion(), context.getTarget());
					continue;
				}
				// check whether the image exist at image-service.
				// if no, delete it from db
				List<JSONObject> results = JSONObject.parseArray(getResult.getMessage(), JSONObject.class);
				if (results.isEmpty()) {
					// image is not exist, delete it from cmp
					imageRepository.deletePhysicalImage(image.getOsType(), image.getOsVersion(), context.getTarget());
				}
			}

			// List all of images from image-service and check with database.
			// If the image miss from database, insert it to db.
			Result newResult = request(null, buildUrl(ImageServiceUrl.IMAGES, null), BoCloudMethod.GET);
			if (newResult.isFailed()) {
				logger.error("request image service failed, {}", newResult);
				return new BocloudResult(false, "与镜像服务器同步数据失败:" + newResult.getMessage());
			}
			List<JSONObject> images = JSONObject.parseArray(newResult.getMessage(), JSONObject.class);
			for (JSONObject image : images) {
				if ("OK".equals(image.getString("status"))) {
					if (!imageRepository.exists(image.getString(Common.NAME), image.getString("version"))) {
						// get the size from image-service
						Result isoResult = request(null, buildUrl(ImageServiceUrl.ISO, null), BoCloudMethod.GET);
						if (isoResult.isFailed()) {
							logger.error("获取image的iso信息失败:{}", isoResult);
							continue;
						}
						List<JSONObject> multiIso = JSONObject.parseArray(isoResult.getMessage(), JSONObject.class);
						// the image don't exist at database. insert it
						String name = image.getString(Common.NAME) + "_" + image.getString("version");
						double size = this.getImageSize(multiIso, image.getString("iso"));
						// 如果计算出来的image的size为-1,说明iso文件以及损坏，镜像不可用，不存数据库
						if (size != -1) {
							Image newImage = new Image(name, image.getString(Common.NAME), image.getString("version"),
									size, image.getString("iso"), IMAGE_STATUS, "从镜像服务器同步的镜像");
							newImage.setCreatorId(0L);
							newImage.setCatalog("PM");
							imageRepository.save(newImage);
						}
					}
				}
			}
			return new BocloudResult(true, "镜像与服务器同步完成");
		} catch (Exception e) {
			logger.error("Failed sync image-service:", e);
			return new BocloudResult(false, "与镜像服务器同步数据失败");
		}
	}

	private double getImageSize(List<JSONObject> multiIso, String isoFile) {
		double size = -1;
		try {
			for (JSONObject iso : multiIso) {
				if (isoFile.equals(iso.getString(Common.NAME))) {
					size = Double.parseDouble(iso.getString("size").split(" ")[0]);
					if ("GB".equalsIgnoreCase(iso.getString("size").split(" ")[1])) {
						size = size * 1024;
					}
					return size;
				}
			}
		} catch (Exception e) {
			logger.error("parse the iso size exception:", e);
		}
		return size;
	}

	@Override
	public BocloudResult remove(Long id, RequestContext context) {
		try {
			Image image = imageRepository.query(id);
			if (image == null) {
				logger.warn("Image data does not exist!");
				return new BocloudResult(false, "镜像不存在");
			}

			// check image used by yum_repo
			BocloudResult repoResult = yumRepoService.list(null, null);
			if (repoResult.isSuccess()) {
				List<YumRepo> repos = JSONObject.parseArray(JSONObject.toJSONString(repoResult.getData()),
						YumRepo.class);
				for (YumRepo repo : repos) {
					if (repo.getBaseurl() != null
							&& repo.getBaseurl().contains(image.getOsType() + "/" + image.getOsVersion())) {
						logger.error("物理机镜像[{}]仍在被软件源[{}]使用，不能删除", image.getName(), repo.getName());
						return new BocloudResult(false,
								"物理机镜像[" + image.getName() + "]仍在被软件源[" + repo.getName() + "]使用，不能删除");
					}
				}
			}

			// delete image from image service
			Map<String, String> paramMap = Maps.newHashMap();
			paramMap.put(Common.NAME, image.getOsType());
			paramMap.put("version", image.getOsVersion());
			Result result = request(null, buildUrl(ImageServiceUrl.IMAGES, paramMap), BoCloudMethod.DELETE);
			if (result.isSuccess()) {
				// remove the image from cmp
				imageRepository.delete(Image.class, id);
				return new BocloudResult(true, "删除镜像成功");
			} else {
				logger.warn("从image-service删除镜像失败,{}", result.getMessage());
				return new BocloudResult(false, "删除镜像失败");
			}
		} catch (Exception e) {
			logger.error("Remove image fail:", e);
			return new BocloudResult(false, "删除失败");
		}
	}

	public List<String> getFileList(String strPath, String nfsPath) {
		List<String> isoFiles = new ArrayList<>();
		File dir = new File(strPath);
		File[] files = dir.listFiles();
		if (files != null) {
			for (int i = 0; i < files.length; i++) {
				String fileName = files[i].getName();
				if (files[i].isDirectory()) {
					isoFiles.addAll(getFileList(files[i].getAbsolutePath(), nfsPath));
				} else if (fileName.endsWith("iso")) {
					// 只获取iso结尾的文件
					String absultPath = files[i].getAbsolutePath();
					isoFiles.add(absultPath.substring(nfsPath.length()));
				}
			}
		}
		return isoFiles;
	}

	@Override
	public BocloudResult isoList() {
		List<String> results = this.getFileList(storageConfig.getPath(), storageConfig.getPath());
		return new BocloudResult(true, results, "查询成功");

	}

	private String buildUrl(final ImageServiceUrl url, Map<String, String> paramMap) {
		StringBuilder builder = new StringBuilder();
		builder.append(Constant.HTTP_PREFIX).append(imageServiceConfig.getIp()).append(":")
				.append(imageServiceConfig.getPort()).append("/").append(url.getValue());
		if (paramMap != null) {
			Set<Map.Entry<String, String>> entrySet = paramMap.entrySet();
			int index = 0;
			for (Map.Entry<String, String> e : entrySet) {
				index++;
				if (index == 1) {
					builder.append("?");
				} else {
					builder.append("&");
				}
                String value = URLEncoder.encode(e.getValue());
                builder.append(e.getKey()).append("=").append(value);
			}
		}
		return builder.toString();
	}

	@SuppressWarnings("unchecked")
	private Result request(String jsonData, String url, BoCloudMethod method) {
		// Map<String, Object> headers = MapTools.simpleMap("Content-Type", "application/json");
        Map<String, Object> headers = new HashMap<>();
        Result result;
		HttpClient client = new HttpClient(1000 * 10, PostDataFormat.RAW);
		Map<String, Object> params = null;
		if (StringUtils.isNotEmpty(jsonData)) {
			params = JSONObject.parseObject(jsonData, HashMap.class);
		}
		switch (method) {
		case GET:
			result = client.get(headers, params, url);
			break;
		case POST:
			result = client.post(headers, params, url);
			break;
		case PATCH:
			result = client.patch(headers, params, url);
			break;
		case PUT:
			result = client.put(headers, params, url);
			break;
		case DELETE:
			result = client.delete(headers, params, url);
			break;
		default:
			result = new Result(false, "请求方法不支持");
			break;
		}
		return result;
	}
}
