package com.younike.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.*;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import com.younike.common.core.domain.VideoUploadResult;
import lombok.extern.log4j.Log4j2;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URI;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author chenpengfei
 * <p>
 * 阿里OSS上传服务
 */
@Component
@Log4j2
public class AliyunOSSUtil {

    @Value("${oss.bucketname}")
    private String bucketName;
    @Value("${oss.filehost}")
    private String getFileHost;
    @Value("${oss.filepath}")
    private String filePath;
    @Value("${oss.endpoint}")
    private String endpoint;
    @Value("${oss.keyid}")
    private String keyid;
    @Value("${oss.keysecret}")
    private String keysecret;
    @Resource
    private OSSClient client;

    private static String fileHost;


//    /**
//     * 获取配置文件中的数据
//     * @throws Exception
//     */
//    @Override
//    public void afterPropertiesSet() throws Exception {
//        AliyunOSSUtil.fileHost = this.getFileHost;
//    }

    /**
     * 上传文件
     */
    public String upLoad(File file, String fileUrl) {
        log.info("------OSS文件上传开始--------" + file.getName());
        // 判断文件是否为空
        if (file == null) {
            return null;
        }

        try {
            // 判断容器是否存在,不存在就创建
            if (!client.doesBucketExist(bucketName)) {
                client.createBucket(bucketName);
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                client.createBucket(createBucketRequest);
                // 设置权限(公开读)
                client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
            }
            // 上传文件
            PutObjectResult result = client.putObject(new PutObjectRequest(bucketName, fileUrl, file));
            if (result != null) {
                log.info("------OSS文件上传成功------" + fileUrl);
                return fileUrl;
            }
        } catch (OSSException oe) {
            log.error(oe.getMessage());
        } catch (ClientException ce) {
            log.error(ce.getErrorMessage());
        } finally {

        }
        return null;
    }

    /**
     * 上传视频文件并截取第一帧
     */
   /* public VideoUploadResult videoUpload(File file, String fileUrl) {
        log.info("------OSS文件上传开始--------" + file.getName());
        // 判断文件是否为空
        if (file == null) {
            return null;
        }
        VideoUploadResult vr = new VideoUploadResult();
        try {
            // 判断容器是否存在,不存在就创建
            if (!client.doesBucketExist(bucketName)) {
                client.createBucket(bucketName);
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                client.createBucket(createBucketRequest);
                // 设置权限(公开读)
                client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
            }
            // 上传文件
            PutObjectResult result = client.putObject(new PutObjectRequest(bucketName, fileUrl, file));
            if (result != null) {
                log.info("------OSS文件上传成功------" + fileUrl);
                vr.setVideoUrl(fileUrl);
            }
            String style = "?x-oss-process=video/snapshot,t_1000,f_jpg,w_0,h_0,m_fast,ar_auto";
            String signedUrl = fileUrl + style;
            if (signedUrl != null) {
                vr.setSignedUrl(signedUrl);
            }
        } catch (OSSException oe) {
            log.error(oe.getMessage());
        } catch (ClientException ce) {
            log.error(ce.getErrorMessage());
        } finally {

        }
        return vr;
    }*/

    /**
     * 2021-9-6 Another 过滤图片显示全路径（若有微信头像直接返回）
     * @param url
     * @return
     */
    public static String translateAddress(String url) {
        /*if (StringUtils.isBlank(url)) {
            return "";
        }
        if (url.contains("https://")) {
            return url;
        }
        return fileHost + url;*/
        return url;
    }

    /**
     * 2021-9-6 Another 过滤图片显示全路径（若有微信头像直接返回）
     * @param urls 通过指定字符串拼接起来的路径
     * @param character 指定分割字符
     * @return
     */
    public static String translateAddressString(String urls, String character) {
        /*StringBuffer urlString = new StringBuffer();
        if (StringUtils.isBlank(urls)) {
            return "";
        }
        String[] split = urls.split(character);
        for (String url : split) {
            if (StringUtils.isNotBlank(urlString)) {
                urlString.append(character);
            }
            if (url.contains("https://")) {
                urlString.append(url);
            } else {
                urlString.append(fileHost + url);
            }
        }
        return urlString.toString();*/
        return urls;
    }

    /**
     * 上传视频文件并截取第一帧
     */
    public VideoUploadResult videoUpload(File file, String fileUrl) {
        log.info("------OSS文件上传开始--------" + file.getName());
        // 判断文件是否为空
        if (file == null) {
            return null;
        }
        VideoUploadResult vr = new VideoUploadResult();
        try {
            // 判断容器是否存在,不存在就创建
            if (!client.doesBucketExist(bucketName)) {
                client.createBucket(bucketName);
                CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                client.createBucket(createBucketRequest);
                // 设置权限(公开读)
                client.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
            }
            // 上传文件
            PutObjectResult result = client.putObject(new PutObjectRequest(bucketName, fileUrl, file));
            if (result != null) {
                log.info("------OSS文件上传成功------" + fileUrl);
                vr.setVideoUrl(fileUrl);
            }
            String style = "?x-oss-process=video/snapshot,t_1000,f_jpg,w_0,h_0,m_fast,ar_auto";
            String signedUrl = fileUrl + style;
            if (signedUrl != null) {
                vr.setSignedUrl(signedUrl);
            }
        } catch (OSSException oe) {
            log.error(oe.getMessage());
        } catch (ClientException ce) {
            log.error(ce.getErrorMessage());
        } finally {

        }
        return vr;
    }


    public String uploadImage(@RequestParam("file") MultipartFile file) throws Exception {
        log.info("图片上传");
        String fileName = file.getOriginalFilename();
        String uploadUrl = "";
        if (StringUtils.isNotBlank(fileName) && StringUtils.isNotBlank(fileName.trim())) {
            log.info("图片上传开始...");
            File newFile = new File(fileName);
            FileOutputStream os = new FileOutputStream(newFile);
            os.write(file.getBytes());
            os.close();
            file.transferTo(newFile);

            // 根据不同文件文件类型-不同日期生成不同文件夹
            Date date = new Date();
            String datePath = DateUtils.formatDateByStyle(date, DatePattern.CN_DATE_BASIC_STYLE4.getDatePattern());

            String timeStamp = String.valueOf(System.currentTimeMillis());
            fileName = timeStamp + fileName.substring(fileName.lastIndexOf("."), fileName.length());

            String path;
            if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".png")) {
                // images
                path = filePath + "images/" + datePath + "/" + fileName;
            } else if (fileName.endsWith(".mp4")) {
                // video
                path = filePath + "video/" + datePath + "/" + fileName;
            } else {
                path = filePath + "other/" + datePath + "/" + fileName;
            }
            // 上传到OSS
            uploadUrl = this.upLoad(newFile, path);
            newFile.delete();
            if (StringUtils.isNotBlank(uploadUrl)) {
                return getFileHost + uploadUrl;
            }
            log.info("图片上传结束...");
        }
        return uploadUrl;
    }



    public String uploadFiles(File file, String fileUrl) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint,keyid,keysecret);
        try {

            ObjectMetadata meta = new ObjectMetadata();
            // 指定上传的内容类型。
            meta.setContentType("text/plain");
            // 文件上传时设置访问权限ACL。
            // meta.setObjectAcl(CannedAccessControlList.Private);

            // 通过UploadFileRequest设置多个参数。
            // 依次填写Bucket名称（例如examplebucket）以及Object完整路径（例如exampledir/exampleobject.txt），Object完整路径中不能包含Bucket名称。
            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName,fileUrl);

            // 通过UploadFileRequest设置单个参数。
            // 填写本地文件的完整路径，例如D:\\localpath\\examplefile.txt。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。
            uploadFileRequest.setUploadFile(file.getPath());
            // 指定上传并发线程数，默认值为1。
            uploadFileRequest.setTaskNum(5);
            // 指定上传的分片大小，单位为字节，取值范围为100 KB~5 GB。默认值为100 KB。
            uploadFileRequest.setPartSize(1 * 1024 * 1024);
            // 开启断点续传，默认关闭。
            uploadFileRequest.setEnableCheckpoint(true);
            // 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传完成后，该文件会被删除。
            // 如果未设置该值，默认与待上传的本地文件同路径，名称为${uploadFile}.ucp。
            uploadFileRequest.setCheckpointFile("yourCheckpointFile");
            // 文件的元数据。
            uploadFileRequest.setObjectMetadata(meta);
            // 设置上传回调，参数为Callback类型。
            //uploadFileRequest.setCallback("yourCallbackEvent");

            // 断点续传上传。
            ossClient.uploadFile(uploadFileRequest);
            return fileUrl;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            return null;
        } catch (Throwable ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            return null;
        } finally {
            // 关闭OSSClient。
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 由前端调用成临时的签名
     * @return
     * @throws UnsupportedEncodingException
     */
    public Map<String, String> createSignature() throws UnsupportedEncodingException {
        String host = getFileHost;
        OSSClient client = new OSSClient(endpoint, keyid, keysecret);
        long expireTime = 30;
        long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
        Date expiration = new Date(expireEndTime);
        PolicyConditions policyConds = new PolicyConditions();
        policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
        policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, filePath);
        String postPolicy = client.generatePostPolicy(expiration, policyConds);
        byte[] binaryData = postPolicy.getBytes("utf-8");
        String encodedPolicy = BinaryUtil.toBase64String(binaryData);
        String postSignature = client.calculatePostSignature(postPolicy);
        Map<String, String> respMap = new LinkedHashMap<String, String>();
        respMap.put("accessid", keyid);
        respMap.put("policy", encodedPolicy);
        respMap.put("signature", postSignature);
        respMap.put("dir", filePath);
        respMap.put("host", host);
        respMap.put("expire", String.valueOf(expireEndTime / 1000));
        JSONObject jasonCallback = new JSONObject();
        jasonCallback.put("callbackUrl", "");
        jasonCallback.put("callbackBody",
                "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
        jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
        String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
        respMap.put("callback", base64CallbackBody);
        return  respMap;
    }

    /**
     * 前端上传成功后
     * OSS回调处理业务
     * @param request
     * @return
     */
    public boolean callbackService(HttpServletRequest request) throws IOException {
        //读取参数
        String ossCallbackBody = GetPostBody(request.getInputStream(),Integer.parseInt(request.getHeader("content-length")));
        log.info("ossCallbackBody:"+ossCallbackBody);
        //验证参数
        boolean bool = VerifyOSSCallbackRequest(request, ossCallbackBody);
        log.info("verify result : " + bool);
        return bool;
    }
    /**
     * 获取Post消息体
     *
     * @param is
     * @param contentLen
     * @return
     */
    private String GetPostBody(InputStream is, int contentLen) {
        if (contentLen > 0) {
            int readLen = 0;
            int readLengthThisTime = 0;
            byte[] message = new byte[contentLen];
            try {
                while (readLen != contentLen) {
                    readLengthThisTime = is.read(message, readLen, contentLen - readLen);
                    if (readLengthThisTime == -1) {// Should not happen.
                        break;
                    }
                    readLen += readLengthThisTime;
                }
                return new String(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    /**
     * 验证上传回调的Request
     *
     * @param request
     * @param ossCallbackBody
     */
    private boolean VerifyOSSCallbackRequest(HttpServletRequest request, String ossCallbackBody)
            throws NumberFormatException, IOException {
        boolean ret = false;
        String autorizationInput = new String(request.getHeader("Authorization"));
        log.info("autorizationInput:"+autorizationInput);
        String pubKeyInput = request.getHeader("x-oss-pub-key-url");
        log.info("pubKeyInput:"+pubKeyInput);
        byte[] authorization = BinaryUtil.fromBase64String(autorizationInput);
        byte[] pubKey = BinaryUtil.fromBase64String(pubKeyInput);
        String pubKeyAddr = new String(pubKey);
        if (!pubKeyAddr.startsWith("http://gosspublic.alicdn.com/")
                && !pubKeyAddr.startsWith("https://gosspublic.alicdn.com/")) {
            System.out.println("pub key addr must be oss addrss");
            return false;
        }
        String retString = executeGet(pubKeyAddr);
        retString = retString.replace("-----BEGIN PUBLIC KEY-----", "");
        retString = retString.replace("-----END PUBLIC KEY-----", "");
        String queryString = request.getQueryString();
        String uri = request.getRequestURI();
        String decodeUri = java.net.URLDecoder.decode(uri, "UTF-8");
        String authStr = decodeUri;
        if (queryString != null && !queryString.equals("")) {
            authStr += "?" + queryString;
        }
        authStr += "\n" + ossCallbackBody;
        ret = doCheck(authStr, authorization, retString);
        return ret;
    }

    /**
     * 验证RSA
     *
     * @param content
     * @param sign
     * @param publicKey
     * @return
     */
    private static boolean doCheck(String content, byte[] sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] encodedKey = BinaryUtil.fromBase64String(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
            java.security.Signature signature = java.security.Signature.getInstance("MD5withRSA");
            signature.initVerify(pubKey);
            signature.update(content.getBytes());
            boolean bverify = signature.verify(sign);
            return bverify;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 获取public key
     * @param url
     * @return
     */
    private String executeGet(String url) {
        BufferedReader in = null;

        String content = null;
        try {
            // 定义HttpClient
            @SuppressWarnings("resource")
            DefaultHttpClient client = new DefaultHttpClient();
            // 实例化HTTP方法
            HttpGet request = new HttpGet();
            request.setURI(new URI(url));
            HttpResponse response = client.execute(request);

            in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            StringBuffer sb = new StringBuffer("");
            String line = "";
            String NL = System.getProperty("line.separator");
            while ((line = in.readLine()) != null) {
                sb.append(line + NL);
            }
            in.close();
            content = sb.toString();
        } catch (Exception e) {
        } finally {
            if (in != null) {
                try {
                    in.close();// 最后要关闭BufferedReader
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return content;
        }
    }
    public String uploadFileSlice(String filePaths, String fileUrl) {

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint,keyid,keysecret);
        try {

            ObjectMetadata meta = new ObjectMetadata();
            // 指定上传的内容类型。
            meta.setContentType("text/plain");
            // 文件上传时设置访问权限ACL。
            // meta.setObjectAcl(CannedAccessControlList.Private);

            // 通过UploadFileRequest设置多个参数。
            // 依次填写Bucket名称（例如examplebucket）以及Object完整路径（例如exampledir/exampleobject.txt），Object完整路径中不能包含Bucket名称。
            UploadFileRequest uploadFileRequest = new UploadFileRequest(bucketName,fileUrl);

            // 通过UploadFileRequest设置单个参数。
            // 填写本地文件的完整路径，例如D:\\localpath\\examplefile.txt。如果未指定本地路径，则默认从示例程序所属项目对应本地路径中上传文件。
            uploadFileRequest.setUploadFile(filePaths);
            // 指定上传并发线程数，默认值为1。
            uploadFileRequest.setTaskNum(5);
            // 指定上传的分片大小，单位为字节，取值范围为100 KB~5 GB。默认值为100 KB。
            uploadFileRequest.setPartSize(1 * 1024 * 1024);
            // 开启断点续传，默认关闭。
            uploadFileRequest.setEnableCheckpoint(true);
            // 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传完成后，该文件会被删除。
            // 如果未设置该值，默认与待上传的本地文件同路径，名称为${uploadFile}.ucp。
            uploadFileRequest.setCheckpointFile("yourCheckpointFile");
            // 文件的元数据。
            uploadFileRequest.setObjectMetadata(meta);
            // 设置上传回调，参数为Callback类型。
            //uploadFileRequest.setCallback("yourCallbackEvent");

            // 断点续传上传。
            ossClient.uploadFile(uploadFileRequest);
            return fileUrl;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            return null;
        } catch (Throwable ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            return null;
        } finally {
            // 关闭OSSClient。
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
}
