package org.weiboad.ragnar.server.statistics.api;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.weiboad.ragnar.server.config.FieryConfig;
import org.weiboad.ragnar.server.storage.DBManage;
import org.weiboad.ragnar.server.storage.DBSharder;
import org.weiboad.ragnar.server.struct.MetaLog;
import org.weiboad.ragnar.server.util.DateTimeHelper;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Scope("singleton")
public class APIStatisticTimeSet {

    private ConcurrentHashMap<Long, ConcurrentHashMap<String, APIStatisticStruct>> apiTopStaticHelper = new ConcurrentHashMap<Long, ConcurrentHashMap<String, APIStatisticStruct>>();
    private ConcurrentHashMap<String, ConcurrentHashMap<Long, APIStatisticStruct>> apiTopHourStaticHelper = new ConcurrentHashMap<>();

    private Logger log = LoggerFactory.getLogger(APIStatisticTimeSet.class);

    @Autowired
    private FieryConfig fieryConfig;

    @Autowired
    private DBManage dbManage;

    public void analyzeMetaLog(MetaLog metainfo) {

        String url = metainfo.getUrl();
        Long shardTime = DateTimeHelper.getTimesMorning(metainfo.getTime().longValue());
        Long hourShardTime = DateTimeHelper.getHourTime(metainfo.getTime().longValue());

        if (url.trim().length() > 0 && shardTime > 0 && shardTime > DateTimeHelper.getCurrentTime() -
                (fieryConfig.getKeepdataday() * 86400)) {

            //day
            if (!apiTopStaticHelper.containsKey(shardTime)) {
                //day shard
                ConcurrentHashMap<String, APIStatisticStruct> urlshard = new ConcurrentHashMap<>();
                APIStatisticStruct urlInfo = new APIStatisticStruct(metainfo, shardTime);
                urlshard.put(url, urlInfo);
                apiTopStaticHelper.put(shardTime, urlshard);

            } else {
                //count ++
                if (!apiTopStaticHelper.get(shardTime).containsKey(url)) {
                    //day
                    APIStatisticStruct apiStruct = new APIStatisticStruct(metainfo, shardTime);
                    apiTopStaticHelper.get(shardTime).put(metainfo.getUrl(), apiStruct);

                } else {
                    //day
                    apiTopStaticHelper.get(shardTime).get(metainfo.getUrl()).analyzeMetaLog(metainfo);

                }
            }

            //hour
            if (!apiTopHourStaticHelper.containsKey(url)) {
                //hour shard
                ConcurrentHashMap<Long, APIStatisticStruct> urlHourshard = new ConcurrentHashMap<>();
                APIStatisticStruct urlHourInfo = new APIStatisticStruct(metainfo, hourShardTime);
                urlHourshard.put(hourShardTime, urlHourInfo);
                apiTopHourStaticHelper.put(url, urlHourshard);
            } else {
                if (!apiTopHourStaticHelper.get(url).containsKey(hourShardTime)) {
                    //hour
                    APIStatisticStruct apiHourStruct = new APIStatisticStruct(metainfo, hourShardTime);
                    apiTopHourStaticHelper.get(metainfo.getUrl()).put(hourShardTime, apiHourStruct);
                } else {
                    //hour
                    apiTopHourStaticHelper.get(metainfo.getUrl()).get(hourShardTime).analyzeMetaLog(metainfo);
                }
            }

        }//check the time

    }

    public Map<String, Integer> getAPITOPStatics() {
        Map<String, Integer> result = new LinkedHashMap<>();

        for (Map.Entry<Long, ConcurrentHashMap<String, APIStatisticStruct>> ent : apiTopStaticHelper.entrySet()) {
            result.put(ent.getKey() + "", ent.getValue().size());
        }
        return result;
    }


    public TreeMap<Long, APIStatisticStruct> getHourDetail(String url, Long shardtime) {
        TreeMap<Long, APIStatisticStruct> urlStatics = new TreeMap<>();

        if (apiTopHourStaticHelper.containsKey(url)) {
            //return apiTopHourStaticHelper.get(url);
            ConcurrentHashMap<Long, APIStatisticStruct> staticsSet = apiTopHourStaticHelper.get(url);

            for (Map.Entry<Long, APIStatisticStruct> statisticItem : staticsSet.entrySet()) {
                if (statisticItem.getKey() >= shardtime && statisticItem.getKey() <= shardtime + 86400) {
                    urlStatics.put(DateTimeHelper.getHour(statisticItem.getKey()), statisticItem.getValue());
                }
            }
        }
        return urlStatics;
    }

    public ConcurrentHashMap<String, APIStatisticStruct> getDaySharder(Long timestamp, boolean create) {
        Long shardTime = DateTimeHelper.getTimesMorning(timestamp);
        if (!apiTopStaticHelper.containsKey(shardTime)) {
            if (create) {
                ConcurrentHashMap<String, APIStatisticStruct> urlshard = new ConcurrentHashMap<>();
                apiTopStaticHelper.put(shardTime, urlshard);
                return urlshard;
            }
            //default not create this one
            return null;
        } else {
            return apiTopStaticHelper.get(shardTime);
        }
    }

    @PostConstruct
    public void loadStaticDb() {
        log.info("load the Statistic info start...");
        Gson jsonHelper = new Gson();

        Map<String, String> dblist = dbManage.getDBFolderList();
        for (Map.Entry<String, String> db : dblist.entrySet()) {
            String dbshard = db.getKey();
            Long dbShardLong;

            //prevent the shard name is not long
            try {
                dbShardLong = Long.valueOf(dbshard);
            } catch (Exception e) {
                continue;
            }

            //init the set
            ConcurrentHashMap<String, APIStatisticStruct> apiStatisticStructMap = new ConcurrentHashMap<>();
            apiTopStaticHelper.put(dbShardLong, apiStatisticStructMap);

            try {
                DBSharder dbHelper = dbManage.getDB(dbShardLong);

                if (dbHelper == null) {
                    log.info("load db fail:" + dbshard);
                    continue;
                }

                String staticStr = dbHelper.get("apitopstatistic");

                if (staticStr == null || staticStr.length() == 0) {
                    log.info("load static db info fail:" + dbshard);
                } else {
                    //recovery the statics
                    String[] staticArray = staticStr.split("\r\n");
                    for (int staticIndex = 0; staticIndex < staticArray.length; staticIndex++) {
                        try {
                            APIStatisticStruct apiStatisticStruct = jsonHelper.fromJson(staticArray[staticIndex], APIStatisticStruct.class);
                            apiTopStaticHelper.get(dbShardLong).put(apiStatisticStruct.getUrl(), apiStatisticStruct);
                        } catch (JsonSyntaxException e) {
                            e.printStackTrace();
                        }
                    }
                }

                String staticHourStr = dbHelper.get("apitophourstatistic");

                if (staticHourStr == null || staticHourStr.length() == 0) {
                    log.info("load static hour db info fail:" + dbshard);
                } else {
                    //recovery the statics
                    String[] staticArray = staticHourStr.split("\r\n");
                    for (int staticIndex = 0; staticIndex < staticArray.length; staticIndex++) {
                        try {
                            APIStatisticStruct apiStatisticStruct = jsonHelper.fromJson(staticArray[staticIndex], APIStatisticStruct.class);
                            if (!apiTopHourStaticHelper.containsKey(apiStatisticStruct.getUrl())) {
                                apiTopHourStaticHelper.put(apiStatisticStruct.getUrl(), new ConcurrentHashMap<>());
                            }
                            apiTopHourStaticHelper.get(apiStatisticStruct.getUrl()).put(apiStatisticStruct.getShardTime(), apiStatisticStruct);
                        } catch (JsonSyntaxException e) {
                            e.printStackTrace();
                        }
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
                log.error("load dbshard:" + dbshard + " error:" + e.getMessage());
            }
        }

    }

    @PreDestroy
    public void dumpStaticDb() {
        log.info("dump the Statistic info start...");

        //loop day
        for (Map.Entry<Long, ConcurrentHashMap<String, APIStatisticStruct>> ent : apiTopStaticHelper.entrySet()) {
            StringBuilder staticSting = new StringBuilder();
            StringBuilder staticHourString = new StringBuilder();

            Long shardTime = ent.getKey();
            ConcurrentHashMap<String, APIStatisticStruct> apiStatisticStructMap = ent.getValue();

            //fetch all day total statics
            for (Map.Entry<String, APIStatisticStruct> urlShard : apiStatisticStructMap.entrySet()) {
                String jsonStr = urlShard.getValue().toJson();
                if (jsonStr.trim().length() > 0) {
                    staticSting.append(jsonStr + "\r\n");
                }

                //store the Hour String
                if (apiTopHourStaticHelper.containsKey(urlShard.getKey())) {
                    Long compareEnd = shardTime + 86400;
                    //check this url hour map
                    for (Map.Entry<Long, APIStatisticStruct> hourStatistic : apiTopHourStaticHelper.get(urlShard.getKey()).entrySet()) {
                        //filter
                        if (hourStatistic.getKey() <= compareEnd && hourStatistic.getKey() >= shardTime) {
                            staticHourString.append(hourStatistic.getValue().toJson() + "\r\n");
                        }
                    }
                }
            }

            log.info("dump the Statistic info:" + shardTime + " len:" + apiStatisticStructMap.size());
            log.info("dump the stattistic hour info:" + shardTime +" len:"+ staticHourString.toString().length());

            DBSharder dbSharder = dbManage.getDB(shardTime);

            //day
            if (staticSting.length() > 0 && dbSharder != null) {
                dbSharder.put("apitopstatistic", staticSting.toString());
            }

            //hour
            if (staticHourString.length() > 0 && dbSharder != null) {
                dbSharder.put("apitophourstatistic", staticHourString.toString());
            }
        }

    }

    @Scheduled(fixedRate = 120000)
    public void cleanUpSharder() {

        //clean up day
        if (apiTopStaticHelper.size() > 0) {
            ArrayList<Long> removeMap = new ArrayList<>();

            for (Map.Entry<Long, ConcurrentHashMap<String, APIStatisticStruct>> ent : apiTopStaticHelper.entrySet()) {
                if (ent.getKey() >= DateTimeHelper.getCurrentTime() - fieryConfig.getKeepdataday() * 86400) {
                    continue;
                }
                removeMap.add(ent.getKey());
            }

            for (Long removeKey : removeMap) {
                log.info("Clean up the API Top Statistic:" + removeKey);
                apiTopStaticHelper.remove(removeKey);
            }
        }

        //clean up hour
        if (apiTopHourStaticHelper.size() > 0) {
            ArrayList<String> removeUrlMap = new ArrayList<>();

            for (Map.Entry<String, ConcurrentHashMap<Long, APIStatisticStruct>> ent : apiTopHourStaticHelper.entrySet()) {
                ArrayList<Long> removeMap = new ArrayList<>();

                for (Map.Entry<Long, APIStatisticStruct> itemEnt : ent.getValue().entrySet()) {
                    if (itemEnt.getKey() >= DateTimeHelper.getCurrentTime() - fieryConfig.getKeepdataday() * 86400) {
                        continue;
                    }
                    removeMap.add(itemEnt.getKey());
                }

                for (Long removeKey : removeMap) {
                    log.info("Clean up the API Top Day Statistic:" + removeKey);
                    apiTopHourStaticHelper.get(ent.getKey()).remove(removeKey);
                    if (apiTopHourStaticHelper.get(ent.getKey()).size() == 0) {
                        removeUrlMap.add(ent.getKey());
                    }
                }
            }

            //remove the url
            for (String removeUrlKey : removeUrlMap) {
                apiTopHourStaticHelper.remove(removeUrlKey);
            }

        }

        //cycle dump the statistics
        dumpStaticDb();

    }
}