package com.cku.thirdparty.oss;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartFile;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PolicyConditions;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Configuration
public class OssApiClient {

	public static final String ALI_OSS = "ali_oss";

	private static final String PATH = "/";

	public static final String HTTPS = "https://";

	@Autowired
	private OSSClient ossClient;

	@Value("${aliyun.oss.accessKeyId}")
	private String accessKeyId;

	@Value("${aliyun.oss.bucketName}")
	private String bucketName;

	@Value("${aliyun.oss.projectName}")
	private String projectName;

	/**
	 * 文件上传：根据module、年、月组成路径，生成新的唯一文件名，存储文件
	 * 
	 * @param file   待上传文件
	 * @param module 文件所属模块 example member,userUpload,banner
	 * @return 上传文件结果
	 */
	public OssUploadResult upload(MultipartFile file, String module) {
		InputStream is = null;
		try {
			// 获取上传文件流
			is = file.getInputStream();
			String originalName = file.getOriginalFilename();
			// 文件上传至阿里云
			OssUploadResult result = this.upload(is, module, originalName);
			return result;
		} catch (IOException e) {
			log.error(e.getMessage());
		} finally {
			if (Objects.nonNull(is)) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * 文件上传：根据module、年、月组成路径，生成新的唯一文件名，存储文件
	 * 
	 * @param fileDir 待上传文件的路径
	 * @param module  文件所属模块 example member,userUpload,banner
	 * @param delFile 上传完毕后，是否删除已上传文件 true 为删除
	 * @return 上传文件结果
	 */
	public OssUploadResult upload(String fileDir, String module, boolean delFile) {
		FileInputStream fis = null;
		try {
			File file = new File(fileDir);
			fis = new FileInputStream(file);
			OssUploadResult result = this.upload(fis, module, file.getName());
			if (delFile && file.exists()) {
				file.delete();
			}
			return result;
		} catch (Exception e) {
			log.error(e.getMessage());
		} finally {
			if (Objects.nonNull(fis)) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * 文件覆盖：用新文件覆盖原有OSS文件
	 * 
	 * @param fileDir  待上传文件的路径
	 * @param filePath OSS上文件存储的路径
	 * @param fileName OSS上文件名称
	 * @param delFile  上传完毕后，是否删除已上传文件 true 为删除
	 */
	public void uploadCover(String fileDir, String objectPath, String objectName, boolean delFile) {
		FileInputStream fis = null;
		try {
			File file = new File(fileDir);
			fis = new FileInputStream(file);
			// 文件上传至阿里云
			ossClient.putObject(bucketName, objectPath + PATH + objectName, fis);
			if (delFile && file.exists()) {
				file.delete();
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		} finally {
			if (Objects.nonNull(fis)) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 公用上传方法
	 * 
	 * @param is           待上传文件的输入流
	 * @param module       文件所属模块
	 * @param originalName 原文件名称
	 * @return 文件上传结果
	 */
	public OssUploadResult upload(InputStream is, String module, String originalName) {
		log.info("================上传OSS开始===================");
		if (!ossClient.doesBucketExist(bucketName)) {
			log.info("================" + bucketName + "不存在，创建Bucket===================");
			// 创建bucket
			ossClient.createBucket(bucketName);
			// 设置oss实例的访问权限：公共读
			ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
		}
		// 构建日期路径：avatar/2019/2/26/文件名
		String filePath = this.getExtensionPath(module);
		String newFileName = UUID.randomUUID().toString().replaceAll("-", "") + getExtension(originalName);
		// 文件上传至阿里云
		ossClient.putObject(bucketName, filePath + PATH + newFileName, is);
		// 获取url地址
		String uploadUrl = this.getOssUrl(filePath, newFileName);
		log.info("================上传OSS结束，文件地址:" + filePath + "===================");
		return OssUploadResult.builder().extensionPath(filePath).originalName(originalName).objectName(newFileName)
				.uploadUrl(uploadUrl).build();
	}

	/**
	 * 公用上传方法
	 * 
	 * @param is           待上传文件的输入流
	 * @param module       文件所属模块
	 * @param originalName 原文件名称
	 * @return 文件上传结果
	 */
	public OssUploadResult uploadTmp(InputStream is, String module, String originalName) {
		log.info("================上传OSS开始===================");
		if (!ossClient.doesBucketExist(bucketName)) {
			log.info("================" + bucketName + "不存在，创建Bucket===================");
			// 创建bucket
			ossClient.createBucket(bucketName);
			// 设置oss实例的访问权限：公共读
			ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
		}
		// 构建日期路径：avatar/2019/2/26/文件名
		String filePath = this.getExtensionPath(module);
		// 文件上传至阿里云
		ossClient.putObject(bucketName, filePath + PATH + originalName, is);
		// 获取url地址
		String uploadUrl = this.getOssUrl(filePath, originalName);
		log.info("================上传OSS结束，文件地址:" + filePath + "===================");
		return OssUploadResult.builder().extensionPath(filePath).originalName(originalName).objectName(originalName)
				.uploadUrl(uploadUrl).build();
	}

	/**
	 * 根据模块获取文件存储路径（module、年、月组成路径）
	 * 
	 * @param module 所属模块
	 * @return 存储路径
	 */
	private String getExtensionPath(String module) {
		Calendar calendar = Calendar.getInstance(Locale.getDefault());
		String path = projectName + PATH + module + PATH + calendar.get(Calendar.YEAR) + PATH
				+ (calendar.get(Calendar.MONTH) + 1);
		return path;
	}

	/**
	 * OSS文件下载到特定目录下
	 * 
	 * @param objectPath 文件在OSS中放置的相对位置，为不包含Bucket名称在内的Object完整路径，例如/member/2022/1
	 * @param objectName 文件在OSS中文件名
	 * @param filePath   下载文件存储本地磁盘的路径
	 */
	public void fileDownload(String objectPath, String objectName, String filePath) {
		try {
			ossClient.getObject(new GetObjectRequest(bucketName, objectPath + PATH + objectName), new File(filePath));
		} catch (Exception e) {
			log.error(e.getMessage());
		}
	}

	/**
	 * @param objectPath 文件在OSS中放置的相对位置，为不包含Bucket名称在内的Object完整路径，例如/member/2022/1
	 * @param objectName 文件在OSS中文件名
	 * @param out        下载后的文件，转换成的输出流
	 */
	public void fileDownload(String objectPath, String objectName, OutputStream out) {
		OSSObject ossObject = null;
		try {
			log.info("======aliyun.oss.bucketName======" + bucketName);
			ossObject = ossClient.getObject(bucketName, objectPath + PATH + objectName);
			InputStream is = ossObject.getObjectContent();
			int read;
			byte b[] = new byte[1024 * 1024];
			read = is.read(b);
			while (read != -1) {
				out.write(b, 0, read);
				read = is.read(b);
			}
		} catch (Exception e) {
			log.error(e.getMessage());
		} finally {
			if (Objects.nonNull(ossObject)) {
				try {
					ossObject.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 删除存放在OSS服务器中的文件
	 * 
	 * @param objectPath 文件在OSS中放置的相对位置，为不包含Bucket名称在内的Object完整路径，例如/member/2022/1
	 * @param objectName 文件在OSS中文件名
	 */
	public void fileDelete(String objectPath, String objectName) {
		// 删除文件或目录。如果要删除目录，目录必须为空。
		ossClient.deleteObject(bucketName, objectPath + PATH + objectName);
	}

	/**
	 * 获取OSS文件访问地址
	 * 
	 * @param objectPath 文件在OSS中放置的相对位置，为不包含Bucket名称在内的Object完整路径，例如/member/2022/1
	 * @param objectName 文件在OSS中文件名
	 * @return
	 */
	public String getOssUrl(String objectPath, String objectName) {
		// 获取url地址
		return HTTPS + bucketName + "." + ossClient.getEndpoint().getHost() + PATH + objectPath + PATH + objectName;
	}

	/**
	 * 获取文件后缀名
	 * 
	 * @param originalName 文件原名称
	 * @return
	 */
	public String getExtension(String originalName) {
		char ch;
		int len;
		if (Objects.isNull(originalName) || (len = originalName.length()) == 0
				|| (ch = originalName.charAt(len - 1)) == '/' || ch == '\\' || ch == '.')
			return "";
		int dotInd = originalName.lastIndexOf('.');
		int sepInd = Math.max(originalName.lastIndexOf('/'), originalName.lastIndexOf('\\'));
		if (dotInd <= sepInd) {
			return "";
		} else {
			return originalName.substring(dotInd).toLowerCase();
		}
	}

	public OssSignatureResult getSignature(String module, String fileName) {
		try {
			long expireTime = 30;
			long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
			Date expiration = new Date(expireEndTime);
			// 构建日期路径：avatar/2019/2/26/文件名
			String filePath = this.getExtensionPath(module);
			String newFileName = filePath + PATH + UUID.randomUUID().toString().replaceAll("-", "")
					+ getExtension(fileName);
			PolicyConditions policyConds = new PolicyConditions();
			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, newFileName);
			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
			byte[] binaryData = postPolicy.getBytes("utf-8");
			String encodedPolicy = BinaryUtil.toBase64String(binaryData);
			String postSignature = ossClient.calculatePostSignature(postPolicy);
			return OssSignatureResult.builder().accessid(accessKeyId).policy(encodedPolicy).signature(postSignature)
					.dir(newFileName).host(HTTPS + bucketName + "." + ossClient.getEndpoint().getHost())
					.expire(String.valueOf(expireEndTime / 1000)).build();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}