package net.northking.cctp.scriptcase.api.app.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import net.dongliu.apk.parser.ApkFile;
import net.dongliu.apk.parser.bean.ApkMeta;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.scriptcase.constants.ScriptConstant;
import net.northking.cctp.scriptcase.db.entity.App;
import net.northking.cctp.scriptcase.db.entity.FileMessage;
import net.northking.cctp.scriptcase.db.service.AppService;
import net.northking.cctp.scriptcase.enums.FileError;
import net.northking.cctp.scriptcase.enums.MobileError;
import net.northking.cctp.scriptcase.enums.PlatformType;
import net.northking.cctp.scriptcase.enums.ScriptCaseAppError;
import net.northking.cctp.scriptcase.tools.Assert;
import net.northking.cctp.scriptcase.tools.IpaUtil;
import net.northking.cctp.scriptcase.tools.MinioPathUtils;
import net.northking.cctp.scriptcase.tools.XMLUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.w3c.dom.Document;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

@Service
public class FileMinioServiceImpl implements FileService {
    private final static Logger logger = LoggerFactory.getLogger(FileMinioServiceImpl.class);
    @Autowired
    private IpaUtil ipaUtil;

    @Autowired
    private SimpleStorageService simpleStorageService;

    @Autowired
    private AppService appService;

    //解析apk
    public FileMessage resolveApk(File file) {
        FileMessage fileMessage = new FileMessage();
        Map<String, Object> result = new HashMap<>();
        //解析应用
        ApkFile apkFile = null;
        try {
            try {
                apkFile = new ApkFile(file);
            } catch (IOException e) {
                logger.error("",e);
                throw new PlatformRuntimeException(ScriptCaseAppError.APK_READ_ERR);
            }
            apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
            ApkMeta apkMeta;
            try {
                apkMeta = apkFile.getApkMeta();
            } catch (IOException e) {
                logger.error("",e);
                throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
            }
            result.put(ScriptConstant.APP_NAME, apkMeta.getName());
            result.put(ScriptConstant.PACKAGE_NAME, apkMeta.getPackageName());
            result.put(ScriptConstant.BUILD_VERSION, apkMeta.getVersionName());
            result.put(ScriptConstant.PLATFORM, PlatformType.getCode(ScriptConstant.MOB_PLATFORM_ANDROID));
            try {
                result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
            } catch (IOException e) {
                logger.error("",e);
                throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
            }
            //封装activity
            String activity = null;
            try {
                activity = getActtivity(apkFile);
            } catch (Exception e) {
                logger.error("",e);
                throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
            }
            result.put(ScriptConstant.ACTIVITY, activity);
            Map<String, Object> manifest = new HashMap<>(8);
            manifest.put(ScriptConstant.MOB_PACKAGE, apkMeta.getPackageName());
            Map<String, Object> application = new HashMap<>(8);
            List<Map<String, String>> launcherActivities = new ArrayList<>();
            Map<String, String> params = new HashMap<>(8);
            params.put(ScriptConstant.NAME, activity);
            launcherActivities.add(params);
            application.put(ScriptConstant.LAUNCHER_ACTIVE, launcherActivities);
            manifest.put(ScriptConstant.APPLICATION, application);


            fileMessage.setMessage(result);
            fileMessage.setManifest(manifest);
        } catch (Exception e) {
            logger.error("",e);
            throw new PlatformRuntimeException(FileError.APK_UPLOAD_FAIL);
        } finally {
            try {
                if (null != apkFile) {
                    apkFile.close();
                }
            } catch (IOException e) {
                logger.error("",e);
            }

        }
        return fileMessage;
    }

    private void downLoadAndReturn(HttpServletResponse response, String[] result) {
        try {
            long t1 = System.currentTimeMillis();
            ServletOutputStream out = response.getOutputStream();
            simpleStorageService.getFileAndDirectResponseToHttp(out,result[1]);
            long t2 = System.currentTimeMillis();
            logger.debug("单独从服务器下载应用文件耗时：{}",(t2-t1)/1000);
        } catch (Exception e) {
            logger.error("文件下载流异常", e);
        }
    }

    @Override
    public void downloadApp(HttpServletResponse response, String appId) {
        Assert.hasText(appId, ScriptConstant.APP_ID_IS_NOT_NULL);
        App app = appService.findByPrimaryKey(appId);
        if (null == app) {
            throw new PlatformRuntimeException(ScriptCaseAppError.APP_IS_DEL);
        }
        String fileId = app.getAppUrl();
        if (null == fileId) {
            logger.debug("文件id不存在");
            return;
        }
        logger.debug("请求应用文件《{}》服务下载报告：{}", app.getAppName(), fileId);
        String[] result = MinioPathUtils.idToPath(fileId);
        try {
            response.setHeader(ScriptConstant.CONTENT_DISPOSITION,
                    ScriptConstant.FILE_NAME + URLEncoder.encode(app.getAppName() + "_" + app.getBuildVersion() + result[2].substring(result[2].lastIndexOf(".")), ScriptConstant.STR_ENCODE));
        } catch (UnsupportedEncodingException e) {
            logger.error("文件名编码格式不支持");
        }
        long t1 = System.currentTimeMillis();
        downLoadAndReturn(response, result);
        long t2 = System.currentTimeMillis();
        logger.debug("下载应用文件完整耗时：{}",(t2-t1)/1000);
    }


    public String getObjectName(String fileName) {
        return UUIDUtil.create32UUID() + fileName;
    }


    public static String getAppSize(long size) {
        DecimalFormat df = new DecimalFormat(ScriptConstant.DECIMAL_FORMAT);
        if (size / (long) (1024 * 1024 * 1024) >= 1) {//GB

            return df.format((double) size / (double) (1024 * 1024 * 1024)) + ScriptConstant.APP_SIZE_GB;

        } else if (size / (long) (1024 * 1024) >= 1 && size / (long) (1024 * 1024 * 1024) < 1) {//MB

            return df.format((double) size / (double) (1024 * 1024)) + ScriptConstant.APP_SIZE_MB;

        } else if (size / (long) (1024) >= 1 && size / (long) (1024 * 1024) < 1) {//KB

            return df.format((double) size / (double) (1024)) + ScriptConstant.APP_SIZE_KB;

        } else if (size / (long) (1024) < 1) {

            return size + ScriptConstant.APP_SIZE_B;

        }
        return ScriptConstant.APP_SIZE_DEFAULT;
    }

    /*
     * 文件流转文件(MultipartFile)对象
     * */
    public static MultipartFile[] ioToMultipartFile(String[] urls, String[] fileNames) {

        //文件流数组
        InputStream[] inputStreams = new InputStream[urls.length];
        MultipartFile[] multipartFiles = new CommonsMultipartFile[urls.length];
        URL requestUrl = null;
        HttpURLConnection urlCon = null;

        for (int i = 0; i < urls.length; i++) {
            try {
                requestUrl = new URL(urls[i]);
            } catch (MalformedURLException e) {
                logger.error("",e);
            }
            try {
                urlCon = (HttpURLConnection) requestUrl.openConnection();
                urlCon.setRequestProperty(ScriptConstant.USER_AGENT, ScriptConstant.APP_VERSION_INFO);
                urlCon.connect();
                if (urlCon.getResponseCode() == 200) {
                    inputStreams[i] = urlCon.getInputStream();
                }
            } catch (IOException e) {
                logger.error("",e);
            } finally {
                if (urlCon == null) {
                    urlCon.disconnect();
                }
            }
        }

        FileItemFactory factory = new DiskFileItemFactory(16, null);
        for (int i = 0; i < inputStreams.length; i++) {

            String fieldName = ScriptConstant.SCREEN_SHOT;
            FileItem item = factory.createItem(fieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileNames[i]);
            OutputStream os = null;
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            try {
                os = item.getOutputStream();
                while ((bytesRead = inputStreams[i].read(buffer, 0, 8192)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.flush();
            } catch (IOException e) {

            } finally {
                if (os != null) {
                    try {
                        os.close();
                    } catch (IOException e) {

                    }
                }
                if (inputStreams[i] != null) {
                    try {
                        inputStreams[i].close();
                    } catch (IOException e) {

                    }
                }
            }
            multipartFiles[i] = new CommonsMultipartFile(item);
        }

        return multipartFiles;
    }

    private String getActtivity(ApkFile apkFile) throws Exception {
        Document doc = XMLUtils.getDocument(apkFile.getManifestXml());
        return XMLUtils.getValue(doc, ScriptConstant.X_PATH);
    }

    @Override
    public FileMessage uploadAndroidLargeApp(String appUrl) {
        FileMessage fileMessage = new FileMessage();
        // 文件大小
        long size = 0;
        Map<String, Object> result = new HashMap<>();
        // 解析应用
        ApkFile apkFile = null;
        File file=null;
        try {
            String[] fileNames = MinioPathUtils.idToPath(appUrl);
            file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
            size = file.length();
            if (size == 0) {
                logger.info("APK文件为空");
                throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
            }
            try {
                apkFile = new ApkFile(file);
            } catch (IOException e) {
                logger.info("new ApkFile(file)解析APK异常信息：{}",e.getMessage());
                throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
            }
            apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
            ApkMeta apkMeta;
            try {
                apkMeta = apkFile.getApkMeta();
            } catch (IOException e) {
                logger.info("apkFile.getApkMeta()解析APK异常信息：{}",e.getMessage());
                throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
            }
            result.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
            result.put(ScriptConstant.APP_NAME, apkMeta.getName());
            result.put(ScriptConstant.PACKAGE_NAME, apkMeta.getPackageName());
            result.put(ScriptConstant.BUILD_VERSION, apkMeta.getVersionName());
            result.put(ScriptConstant.PLATFORM, PlatformType.getCode(ScriptConstant.MOB_PLATFORM_ANDROID));
            result.put(ScriptConstant.APP_URL, appUrl);
            try {
                result.put(ScriptConstant.APP_ICON, apkFile.getAllIcons().get(0).getData());
            } catch (IOException e) {
                logger.error("",e);
                throw new PlatformRuntimeException(MobileError.APP_ICON_GET_ERROR);
            }
            // 封装activity
            String activity = null;
            try {
                activity = getActtivity(apkFile);
            } catch (Exception e) {
                logger.info("getActtivity(apkFile)解析APK异常信息：{}",e.getMessage());
                throw new PlatformRuntimeException(MobileError.APP_RESOLUTION_ERROR);
            }
            result.put(ScriptConstant.ACTIVITY, activity);

            Map<String, Object> manifest = new HashMap<>(8);
            manifest.put(ScriptConstant.MOB_PACKAGE, apkMeta.getPackageName());
            Map<String, Object> application = new HashMap<>(8);
            List<Map<String, String>> launcherActivities = new ArrayList<>();
            Map<String, String> params = new HashMap<>(8);
            params.put(ScriptConstant.NAME, activity);
            launcherActivities.add(params);
            application.put(ScriptConstant.LAUNCHER_ACTIVE, launcherActivities);
            manifest.put(ScriptConstant.APPLICATION, application);

            fileMessage.setFileSaveUrl(appUrl);
            fileMessage.setFileUrl(appUrl);
            fileMessage.setMessage(result);
            fileMessage.setManifest(manifest);
        } catch (PlatformRuntimeException e) {
            throw e;
        } catch (FileDownloadException e) {
            logger.error("",e);
        } finally {
            try {
                if (null != apkFile) {
                    apkFile.close();
                }
                if(file!=null){
                    boolean delete = file.delete();
                    if (!delete) {
                        logger.error("删除文件失败");
                    }
                }
            } catch (IOException e) {
                logger.error("",e);
            }
        }
        return fileMessage;
    }

    @Override
    public FileMessage uploadIosLargeApp(String appUrl) {
        FileMessage fileMessage = new FileMessage();
        Map<String, Object> map = new HashMap<>();
        long size = 0;
        File file =null;
        //解析应用
        try {
            String[] fileNames = MinioPathUtils.idToPath(appUrl);
            file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
            size = file.length();
            if (size == 0) {
                logger.info("IPA文件为空");
                throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
            }
            map = IpaUtil.getVersionInfo(file);
        } catch (PlatformRuntimeException e) {
            throw e;
        } catch (Exception e) {
            logger.error("",e);
            throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL);
        }
        if (map.get(ScriptConstant.STATUS_ERR) != null) {
            throw new PlatformRuntimeException(FileError.IPA_UPLOAD_FAIL);
        }
        Map<String, Object> manifest = new HashMap<>(8);
        manifest.put(ScriptConstant.MOB_PACKAGE, map.get(ScriptConstant.PACKAGE_NAME));
        manifest.put(ScriptConstant.VERSION_CODE, map.get(ScriptConstant.VERSION_CODE));
        manifest.put(ScriptConstant.VERSION_NAME, map.get(ScriptConstant.BUILD_VERSION));
        fileMessage.setFileSaveUrl(appUrl);
        fileMessage.setFileUrl(appUrl);
        map.put(ScriptConstant.APP_URL, appUrl);
        map.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
        fileMessage.setMessage(map);
        fileMessage.setManifest(manifest);
        try {
            boolean delete = file.delete();
            if (!delete) {
                logger.error("删除文件失败");
            }
        }catch (Exception e){
            logger.error("",e);
        }
        return fileMessage;
    }

    @Override
    public FileMessage uploadHarmonyOSLargeApp(String appUrl) {
        FileMessage fileMessage = new FileMessage();
        long size = 0;
        File file=null;
        InputStream inputStream = null;
        ZipInputStream zin = null;
        ZipFile zf = null;
        FileInputStream fileInputStream = null;
        File apkFile = null;
        //解析应用
        try {
            String[] fileNames = MinioPathUtils.idToPath(appUrl);
            file = simpleStorageService.downloadAsFile(fileNames[0], "/" + fileNames[1]);
            size = file.length();
            if (size == 0) {
                logger.info("hap文件为空");
                throw new PlatformRuntimeException(MobileError.APP_IS_NULL);
            }
            //apk文件流
            InputStream apkIn = null;
            //apk文件名
            String apkFileName = null;
            //hapConfig
            String hapConfig = "";

            //设置编码
            Charset charset = Charset.forName(ScriptConstant.CHARSET_FOR_NAME);
            
            try {
                // hap包放进输入流
                fileInputStream = new FileInputStream(file);
            } catch (FileNotFoundException e) {
                logger.error("",e);
            }
            inputStream = new BufferedInputStream(fileInputStream);
            // 压缩包输入流
            zin = new ZipInputStream(inputStream, charset);
            // 压缩包文件
            zf = new ZipFile(file);

            //获取config以及apk文件
            ZipEntry ze;
            while ((ze = zin.getNextEntry()) != null) {
                //遍历的当前文件名
                String fileName = ze.getName();
                //当遍历到config.json
                if (ScriptConstant.CONFIG_JSON_NAME.equals(fileName)) {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(zf.getInputStream(ze)));
                    String line;
                    while ((line = reader.readLine()) != null) {
                        hapConfig += line;
                    }
                    if (reader != null) {
                        reader.close();
                    }
                }
                //当遍历到apk文件
                if (ScriptConstant.FILE_ENDWITH_APK.equals(fileName.substring(fileName.length() - 4))) {
                    apkIn = zf.getInputStream(ze);
                    apkFileName = fileName;
                }
            }
            // 解析
            // 将config.json转成map
            JSONObject apkConfigJsonObject = JSON.parseObject(hapConfig);

            // 将apk流转化为文件
            apkFile = new File(apkFileName);
            FileUtils.copyInputStreamToFile(apkIn, apkFile);
            //解析apk得到信息
            fileMessage = resolveApk(apkFile);
            Map<String, Object> message = fileMessage.getMessage();
            message.put(ScriptConstant.APP_SIZE, FileMinioServiceImpl.getAppSize(size));
            message.put(ScriptConstant.PLATFORM, PlatformType.getCode(ScriptConstant.PLATFORM_HARMONY));
            message.put(ScriptConstant.APP_URL, appUrl);
            fileMessage.setFileSaveUrl(appUrl);
            fileMessage.setFileUrl(appUrl);
            fileMessage.setMessage(message);
            
        } catch (PlatformRuntimeException e) {
            throw e;
        } catch (Exception e) {
            logger.error("",e);
            throw new PlatformRuntimeException(ScriptCaseAppError.HAP_ERR);
        }finally {
            if (zf != null) {
                try {
                    zf.close();
                } catch (IOException e) {
                    logger.error("",e);
                }
            }
            if (zin != null) {
                try {
                    zin.close();
                } catch (IOException e) {
                    logger.error("",e);
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error("",e);
                }
            }
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    logger.error("",e);
                }
            }
            if (apkFile != null && apkFile.exists()) {
                boolean delete = apkFile.delete();
                if (!delete) {
                    logger.error("删除文件失败");
                }
            }
            if (file != null) {
                boolean delete = file.delete();
                if (!delete) {
                    logger.error("删除文件失败");
                }
            }
        }
        return fileMessage;
    }
}
