package com.reyun.saas.mob.user.thread.workorder;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.text.csv.CsvWriter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.json.JSONUtil;
import com.amazonaws.services.s3.model.AmazonS3Exception;
import com.reyun.saas.common.constants.WorkOrderExportEnum;
import com.reyun.saas.common.framework.component.AwsS3Component;
import com.reyun.saas.common.properties.CommonProperties;
import com.reyun.saas.common.util.CompressUtil;
import com.reyun.saas.common.util.EquipmentStatusUtils;
import com.reyun.saas.common.util.SpringContextUtil;
import com.reyun.saas.mob.user.dao.AppMapper;
import com.reyun.saas.mob.user.dao.ChannelMapper;
import com.reyun.saas.mob.user.dao.WorkOrderMapper;
import com.reyun.saas.mob.user.domain.App;
import com.reyun.saas.mob.user.domain.Channel;
import com.reyun.saas.mob.user.domain.WorkOrderExport;
import com.reyun.saas.mob.user.thread.model.LogExportModel;
import com.reyun.saas.report.data.dao.DataInfoDayMapper;
import com.reyun.saas.report.util.ReportParamsUril;
import com.reyun.saas.report.vo.ReportParamsVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;

import java.io.*;
import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;

@Slf4j
public class WorkOrderThread implements Runnable {
    private static final DecimalFormat df = new DecimalFormat("#0.00");


    /**
     * 日志桶
     */
    private static final String LOGBUCKETNAME = "yunyu";

    /**
     * s3日志文件的key模板
     */
    private static final String FILEKEYFORMAT = "dna_download/%s/ds=%s/appid=%s/%s-r-00000.gz";

    /**
     * s3文件过期时间
     */
    private static final Long FILEPASTTIME = 1000L * 60L * 60L * 24L * 30L;

    /**
     * 需要导出的工单
     */
    private WorkOrderExport workOrderExport;

    /**
     * 从IOC容器中获取WorkOrderExportMapper
     */
    private WorkOrderMapper WorkOrderMapper = SpringContextUtil.getBean(WorkOrderMapper.class);

    /**
     * 从IOC容器中获取AppMapper
     */
    private AppMapper appMapper = SpringContextUtil.getBean(AppMapper.class);

    /**
     * 从IOC容器中获取AwsS3Component
     */
    private AwsS3Component awsS3Component = SpringContextUtil.getBean(AwsS3Component.class);

    private DataInfoDayMapper dataInfoDayMapper = SpringContextUtil.getBean(DataInfoDayMapper.class);

    private ChannelMapper channelMapper = SpringContextUtil.getBean(ChannelMapper.class);
    /**
     * 从IOC容器中获取CommonProperties
     */
    private CommonProperties commonProperties = SpringContextUtil.getBean(CommonProperties.class);

    public WorkOrderThread() {
    }

    public WorkOrderThread(WorkOrderExport workOrderExport) {
        this.workOrderExport = workOrderExport;

    }

    @Override
    public void run() {

        log.info("开始生成日志 创建人:{} appkey:{} 类型:{} 所属模块:{} 导出工单任务的id:{}", workOrderExport.getCreateUser()
                , workOrderExport.getAppkey(), workOrderExport.getType(), workOrderExport.getModel(), workOrderExport.getId());

        if (WorkOrderExportEnum.TYPE_REPORT.getCode().equals(workOrderExport.getType())) {
            //报表
            generateReport();

        } else if (WorkOrderExportEnum.TYPE_LOG.getCode().equals(workOrderExport.getType())) {
            //日志
            generateLog();

        }


    }


    /**
     * 修改工单生成状态
     */
    private void updateStatus(WorkOrderExport workOrderExpoet, String status, String msg) {
        workOrderExpoet.setStatus(status);
        workOrderExpoet.setMsg(msg);
        WorkOrderMapper.updateByPrimaryKeySelective(workOrderExpoet);
    }


    /**
     * 生成报表
     */
    private void generateReport() {

        WorkOrderExport workOrderExportOne = WorkOrderMapper.selectByPrimaryKey(this.workOrderExport.getId());
        if (!WorkOrderExportEnum.EXPORT_INIT.getCode().equals(workOrderExportOne.getStatus())) {
            log.info("其他线程正在处理该任务 工单id : {}",workOrderExport.getId());
            return;
        }

        updateStatus(this.workOrderExport,WorkOrderExportEnum.EXPORT_DOWNLOADING.getCode(),WorkOrderExportEnum.EXPORT_DOWNLOADING.getName());
        String source = this.workOrderExport.getSource();
        ReportParamsVO reportParamsVO = JSONUtil.toBean(source, ReportParamsVO.class);
        //reportParamsVO 需要格式化处理appkey和实时离线日期区分
        reportParamsVO = ReportParamsUril.handAppkeys(reportParamsVO);
        reportParamsVO = ReportParamsUril.handDate(reportParamsVO);
        //渠道质量排行
        List<String> name = Arrays.asList("渠道名称", "渠道ID","总数","正常设备数量","正常占比","异常设备数量","异常占比");
        List<String> column = Arrays.asList("channel_alias", "cid","num_total","num_normal","rate_normal","num_exception","rate_exception");
        List<Map> maps = dataInfoDayMapper.channelQualityTopList(reportParamsVO);
        List<String> title = CollUtil.newArrayList(name);
        List<List<String>> rows = new ArrayList<>();
        rows.add(title);
        if (!CollectionUtils.isEmpty(maps)) {
            List<Channel> channels = channelMapper.findByAppkey(reportParamsVO.getAppkeys());
            Map<String, String> channelAliasMap = new HashMap<>();
            for (Channel channel : channels) {
                channelAliasMap.put(channel.getCid(), channel.getChannelAlias());
            }
            for (Map map : maps) {
                map.put("rate_exception", df.format(map.get("rate_exception"))+"%");//格式化小数点
                map.put("rate_normal", df.format(map.get("rate_normal"))+"%");//格式化小数点
                Object cid = map.get("cid");
                map.put("channel_alias", channelAliasMap.get(cid) == null ? cid : channelAliasMap.get(cid));
                List<String> val = new ArrayList<>();
                for (String s : column) {
                    val.add(map.get(s).toString());
                }
                rows.add(val);
            }
        }
        //统计分析

            String localFile = commonProperties.getTempFilePath() + "/" + workOrderExport.getWorkOrderName() + ".csv";
            //指定路径和编码
            CsvWriter writer = CsvUtil.getWriter(localFile, CharsetUtil.CHARSET_UTF_8);
            writer.write(rows);
            writer.close();
            BufferedInputStream bufferedInputStream = null;
            File file = new File(localFile);
            try {
                bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
                awsS3Component.uploadStreamToS3(awsS3Component.getBucketName(), "work_order/report/" + FileUtil.getName(file), bufferedInputStream, null, file.length(), FILEPASTTIME);
                this.workOrderExport.setFileSize(file.length());
                updateStatus(this.workOrderExport,WorkOrderExportEnum.EXPORT_COMPLETE.getCode(),WorkOrderExportEnum.EXPORT_COMPLETE.getName());
            } catch (FileNotFoundException e) {
                log.error("S3工单报表文件上传异常", e);
                updateStatus(this.workOrderExport,WorkOrderExportEnum.EXPORT_FAILED.getCode(),e.getMessage());
            } finally {
                IoUtil.close(bufferedInputStream);
                FileUtil.del(file);
            }


    }


    /**
     * 生成日志
     */
    private void generateLog() {

        WorkOrderExport workOrderExportOne = WorkOrderMapper.selectByPrimaryKey(this.workOrderExport.getId());
        if (!WorkOrderExportEnum.EXPORT_INIT.getCode().equals(workOrderExportOne.getStatus())) {
            log.info("其他线程正在处理该任务 工单id : {}",workOrderExport.getId());
            return;
        }

        //修改工单生成状态
        updateStatus(this.workOrderExport, WorkOrderExportEnum.EXPORT_DOWNLOADING.getCode(), WorkOrderExportEnum.EXPORT_DOWNLOADING.getName());

        //上传s3的流
        BufferedInputStream bufferedInputStream = null;

        File file = null;
        try {
            //日志筛选条件
            Map map = JSONUtil.toBean(this.workOrderExport.getSource(), Map.class);

            long start = System.currentTimeMillis();
            file = localGenerateFile(map);

            if (file == null) {
                //修改导出状态
                updateStatus(this.workOrderExport, WorkOrderExportEnum.EXPORT_FAILED.getCode(), WorkOrderExportEnum.EXPORT_FAILED.getName());
                log.error("临时日志文件生成失败 工单名称:{} 工单id:{}  创建人 : {}", this.workOrderExport.getWorkOrderName(),
                        this.workOrderExport.getId(), this.workOrderExport.getCreateUser());
                return;
            }
            bufferedInputStream = new BufferedInputStream(new FileInputStream(file));

            //上传s3
            awsS3Component.uploadStreamToS3(awsS3Component.getBucketName(), "work_order/log/" + file.getName(), bufferedInputStream, null, file.length(), FILEPASTTIME);

            //修改工单生成状态
            this.workOrderExport.setFileSize(file.length());
            updateStatus(this.workOrderExport, WorkOrderExportEnum.EXPORT_COMPLETE.getCode(), WorkOrderExportEnum.EXPORT_COMPLETE.getName());
            long end = System.currentTimeMillis();
            log.info("日志生成成功 创建人:{} appkey:{} 类型:{} 所属模块:{} 工单id:{} 耗时:{}毫秒", this.workOrderExport.getCreateUser()
                    , this.workOrderExport.getAppkey(), this.workOrderExport.getType(), this.workOrderExport.getModel(), this.workOrderExport.getId(), (end - start));
        } catch (Exception e) {
            //修改工单生成状态  发生异常 改为失败
            updateStatus(this.workOrderExport, WorkOrderExportEnum.EXPORT_FAILED.getCode(), e.getMessage());
            log.error("日志生成失败! 工单id:{}  创建人:{}  异常信息:{}", this.workOrderExport.getId(), this.workOrderExport.getCreateUser(), e);
        } finally {
            //关流
            IoUtil.close(bufferedInputStream);
            //删除临时文件
            FileUtil.del(file);
        }

    }


    /**
     * 生成日志 本地生成临时文件(压缩之后的)
     */
    private File localGenerateFile(Map<String, Object> param) throws IOException {

        //获取app
        Example example = new Example(App.class);
        example.and().andEqualTo("appkey", workOrderExport.getAppkey());
        example.and().andEqualTo("delFlag", false);
        List<App> apps = appMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(apps)) {
            log.warn("app不存在导出日志失败 工单id:{} appkey : {}", workOrderExport.getId(), workOrderExport.getAppkey());
            return null;
        }
        param.put("app", apps.get(0));

        //获取渠道
        String os = (String) param.get("os");
        //ios无需获取渠道
        if ("Android".equals(os)) {
            List<String> cids = param.get("cid") != null ? (List<String>) param.get("cid") : null;

            Map<String, Channel> channelMap = null;
            Example channel = new Example(Channel.class);
            if (!CollectionUtils.isEmpty(cids)) {
                channel.and().andEqualTo("appkey", workOrderExport.getAppkey());
                channel.and().andIn("cid", cids);
            }else{
                channel.and().andEqualTo("appkey", workOrderExport.getAppkey());
            }
            List<Channel> channels = channelMapper.selectByExample(channel);
            channelMap = channels.stream().collect(Collectors.toMap(Channel::getCid, v -> v, (v1, v2) -> v1));
            param.put("channelMap", channelMap);
        }



        //临时文件存放位置
        String tempFilePath = commonProperties.getTempFilePath();

        File temp = null;
        File gzTemp = null;
        Boolean isException = false;
        FileWriter fileWriter = null;
        PrintWriter printWriter = null;

        try {
            //父级目录
            File parentFile = new File(tempFilePath);
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }

            //临时文件
            temp = new File(parentFile, workOrderExport.getWorkOrderName() + ".csv");

            //文件不存在 创建文件  文件存在追加内容
            fileWriter = new FileWriter(temp, true);
            printWriter = new PrintWriter(fileWriter);

            //写表头
            String equipmentInfo = commonProperties.getEquipmentInfo();
            printWriter.print(equipmentInfo + "\r\n");
            printWriter.flush();


            //获取s3日志文件的键的集合
            List<String> keys = getKeys(param);

            //获取s3中的日志文件
            for (String key : keys) {

                BufferedReader inputStreamFromOss = null;
                try {
                    try {
                        inputStreamFromOss = getInputStreamFromOss(LOGBUCKETNAME, key);
                    } catch (AmazonS3Exception e) {
                        log.warn("s3获取日志失败 Bucket:{}  key:{}", LOGBUCKETNAME, key);
                        continue;
                    }
                    //计数器
                    int count = 0;
                    //日志内容
                    StringBuilder stringBuilder = new StringBuilder();

                    String line = null;
                    while ((line = inputStreamFromOss.readLine()) != null) {

                        String[] split = line.split("\t");
                        LogExportModel logExportModel = generateLogModel(split, param);
                        if (logExportModel == null) {
                            continue;
                        }
                        //logExportModel.getLog()中有换行
                        stringBuilder.append(logExportModel.getLog());
                        count++;

                        //拼接50次写一波
                        if (count == 50) {
                            printWriter.print(stringBuilder.toString());
                            printWriter.flush();
                            stringBuilder = null;
                            stringBuilder = new StringBuilder();
                            count = 0;
                        }
                    }
                    //写剩下的内容
                    if (count != 0) {
                        printWriter.print(stringBuilder.toString());
                        printWriter.flush();
                    }
                } finally {
                    //关闭s3的流
                    IoUtil.close(inputStreamFromOss);
                }
            }


            //压缩文件
            gzTemp = CompressUtil.compress(temp);

        } catch (Exception e) {
            //发生异常
            log.error("临时日志文件生成失败 工单名称:{} 工单id:{}  创建人 : {}  异常信息:{}", workOrderExport.getWorkOrderName(),
                    workOrderExport.getId(), workOrderExport.getCreateUser(),e.getMessage());
            isException = true;
            //根据返回的文件是否为空判断是否生成成功
            gzTemp = null;
        }finally {
            //关闭流
            IoUtil.close(printWriter);
            //删除临时文件
            FileUtil.del(temp);
            //如果发生异常删除压缩文件
            if (isException) {
                FileUtil.del(gzTemp);
            }

        }


        return gzTemp;
    }


    /**
     * 封装日志数据模型
     */
    public LogExportModel generateLogModel(String[] data, Map<String, Object> param) {

        String os = (String)param.get("os");
        String channelAlias = "-";
        String chuannelId = "-";

        //获取app
        App app = (App) param.get("app");

        //查询渠道  如果是ios无需查询渠道
        if ("Android".equals(os)) {
            //获取渠道
            Map<String, Channel> channels = param.get("channelMap") != null ? (Map<String, Channel>) param.get("channelMap") : null;
            chuannelId = data[5];
            if (channels != null && channels.size() == 0) {
               //全部渠道
                Channel channel = channels.get(chuannelId);
                if (channel != null) {
                    channelAlias  = channel.getChannelAlias();
                }else {
                    channelAlias = "-";
                }
            }else{
                //根据渠道过滤数据
                if (channels.containsKey(chuannelId)) {
                    channelAlias = channels.get(chuannelId).getChannelAlias();
                }else{
                    return null;
                }
            }
        }else{
            //ios
            channelAlias = "appleStore";
        }

        //封装列LogExportModel
        LogExportModel log = new LogExportModel();

        Date dateTime = DateUtil.parse(data[0]);
        //上报时间,上报日期,应用名称,应用包名,app版本,渠道别名,渠道id,rdid,用户状态,作弊状态,是否更新,idfa,idfv,imei,oaid,androidid,sdk版本,ip
        log.setEventTime(DateUtil.format(dateTime, "yyyy-MM-dd HH:mm:ss"));
        log.setDate(DateUtil.format(dateTime, "yyyy-MM-dd"));
        log.setAppName(app.getAppName());
        log.setBundleid(data[3]);
        log.setAppVersion(data[4]);
        log.setChannelAlias(channelAlias);
        log.setCid(chuannelId);
        log.setRdid(data[6]);


        String equipmentStatus = EquipmentStatusUtils.getEquipmentStatus(data[7]);
        log.setEquipmentStatus(equipmentStatus);//设备状态

        String cheatingBehavior = EquipmentStatusUtils.getCheatingBehavior(data[8]);
        log.setCheatingBehavior(cheatingBehavior);//作弊状态

        String updateStatus = EquipmentStatusUtils.getUpdateStatus(data[9]);
        log.setIsUpdate(updateStatus);//更新状态

        log.setIdfa(data[10]);
        log.setIdfv(data[11]);
        log.setImei(data[12]);
        log.setOaid(data[13]);
        log.setAndroidid(data[14]);
        log.setSdkVersion(data[15]);
        log.setIp(data[16]);

        return log;
    }

    /**
     * 获取s3中日志的key集合
     */
    public List<String> getKeys(Map map) {

        /**
         * 系统类型
         */
        String os = (String) map.get("os");

        List<String> result = new LinkedList<>();

        Calendar instance = Calendar.getInstance();
        //时间
        Long currentDate = DateUtil.parse(workOrderExport.getBeginDate()).getTime();
        //结束时间
        Long endDate = DateUtil.parse(workOrderExport.getEndDate()).getTime();
        while (currentDate <= endDate) {
            String date = DateUtil.format(new Date(currentDate), "yyyy-MM-dd");
            if ("iOS".equals(os)) {
                os = "ios";
            } else if ("Android".equals(os)) {
                os = "android";
            }
            String filekey = String.format(FILEKEYFORMAT, os,date,workOrderExport.getAppkey(),date);
            result.add(filekey);

            //迭代部分(天数加一)
            instance.setTimeInMillis(currentDate);
            instance.add(Calendar.DATE, 1);
            currentDate = instance.getTimeInMillis();
        }
        return result;
    }


    /**
     * 获取s3的日志文件
     *
     * @param readBucket 桶名
     * @param key        文件key
     * @return BufferedReader流
     * @throws IOException
     */
    public BufferedReader getInputStreamFromOss(String readBucket, String key) throws IOException {
        InputStream inputStream = awsS3Component.downloadStreamFromS3(readBucket, key);
        //gz输入流
        GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream);
        BufferedReader br = new BufferedReader(new InputStreamReader(gzipInputStream));
        return br;
    }


}
