package com.smppw.utils;

import com.smppw.common.cache.CaffeineLocalCache;
import com.smppw.constants.DateConst;
import com.smppw.common.pojo.IndexesTradeDateDo;
import com.smppw.common.pojo.dto.DateValue;
import com.smppw.common.pojo.dto.calc.IndicatorCalcTimeRangeDto;
import com.smppw.common.pojo.dto.indicator.DateIntervalDto;
import com.smppw.common.pojo.dto.time.TimeIntervalDto;
import com.smppw.common.pojo.enums.DateIntervalType;
import com.smppw.common.pojo.enums.Frequency;
import com.smppw.common.pojo.enums.TimeRange;
import com.smppw.common.timerange.TimeRangeFactory;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

public class DateIntervalUtil {

    /**
     * 根据交易日序列获取区间时段
     * @param startDateStr
     * @param endDateStr
     * @param rollingFrequency
     * @param navList
     * @param indexesTradeDateDoList
     * @return
     */
    public static List<IndicatorCalcTimeRangeDto> getDefualtRollingInterval(String startDateStr, String endDateStr,
                                                                            Frequency rollingFrequency, List<DateValue> navList, List<IndexesTradeDateDo> indexesTradeDateDoList, String id) {
        List<DateValue> dateValues = NavUtil.filterDefaultRollingFrequencyNav(navList, indexesTradeDateDoList, rollingFrequency, endDateStr);

        if (StringUtils.isEmpty(startDateStr)) {
            return new ArrayList<>();
        }

        Map<Date, IndexesTradeDateDo> tradeDateDoMap = new HashMap<>();
        for (IndexesTradeDateDo indexesTradeDateDo : indexesTradeDateDoList) {
            tradeDateDoMap.put(DateUtil.StringToDate(indexesTradeDateDo.getTradeDate(), DateConst.YYYY_MM_DD), indexesTradeDateDo);
        }
        List<IndicatorCalcTimeRangeDto> indicatorCalcTimeRangeDtoList = new ArrayList<>();

        String start = "";
        boolean inInterval = false;
        for (DateValue dateValue : dateValues) {
            if (dateValue.getIsvalid() != 1) {
                continue;
            }
            String dateStr = dateValue.getDate();
            if (!inInterval) {
                if (dateStr.compareTo(startDateStr) >= 0) {
                    start = dateStr;
                    inInterval = true;
                }
                continue;
            }
            if (dateStr.compareTo(endDateStr) >= 0) {
                dateStr = endDateStr;
            }
            if (start.compareTo(dateStr) >= 0) {
                continue;
            }

            if (rollingFrequency != Frequency.Weekly) {
                Date dateStrMonthDate = DateUtil.addDay(dateStr, -5, DateConst.YYYY_MM_DD);
                Date startMonthDate = DateUtil.addDay(start, -5, DateConst.YYYY_MM_DD);

                String dateStrMonth = DateUtil.DateToString(dateStrMonthDate, DateConst.YYYY_MM);
                String startMonth = DateUtil.DateToString(startMonthDate, DateConst.YYYY_MM);
                if (dateStrMonth.equals(startMonth)) {
                    continue;
                }
            } else {
                String dateStrYearWeek = CaffeineLocalCache.getYearWeekByDate(dateStr);
                String startYearWeek = CaffeineLocalCache.getYearWeekByDate(start);
                if (dateStrYearWeek.equals(startYearWeek)) {
                    continue;
                }
            }

            IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto = new IndicatorCalcTimeRangeDto();
            if (rollingFrequency == Frequency.Weekly) {
                Date endDate = DateUtil.StringToDate(dateStr, DateConst.YYYY_MM_DD);
                if (tradeDateDoMap.get(endDate) == null) {
                    continue;
                }
                if (StringUtils.isEmpty(id)) {
                    indicatorCalcTimeRangeDto.setId(tradeDateDoMap.get(endDate).getYearWeek());
                } else {
                    indicatorCalcTimeRangeDto.setId(id);
                }
            } else {
                Date monthDate = DateUtil.addDay(dateStr, -5, DateConst.YYYY_MM_DD);
                if (tradeDateDoMap.get(monthDate) == null) {
                    continue;
                }
                if (StringUtils.isEmpty(id)) {
                    indicatorCalcTimeRangeDto.setId(tradeDateDoMap.get(monthDate).getYearmonth());
                } else {
                    indicatorCalcTimeRangeDto.setId(id);
                }

            }
            indicatorCalcTimeRangeDto.setStartDate(start);
            indicatorCalcTimeRangeDto.setEndDate(dateStr);
            if (rollingFrequency == Frequency.Weekly) {
                indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Last1Week);
            } else if (rollingFrequency == Frequency.Monthly) {
                indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Last1Month);
            } else if (rollingFrequency == Frequency.Quarterly) {
                indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Last3Months);
            } else if (rollingFrequency == Frequency.Annually) {
                indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Last1Year);
            }

            indicatorCalcTimeRangeDtoList.add(indicatorCalcTimeRangeDto);
            start = dateStr;
            if (dateStr.compareTo(endDateStr) >= 0) {
                break;
            }
        }
        return indicatorCalcTimeRangeDtoList;
    }

    public static List<IndicatorCalcTimeRangeDto> getCustomRollingInterval(String startDateStr, String endDateStr,
                                                                           Integer rollingWindow, Integer rollingStep, String id) {
        if (rollingWindow == 0 || rollingStep == 0) {
            return new ArrayList<>();
        }

        Date rollingStartDate = DateUtil.StringToDate(startDateStr);
        Calendar rollingStartDateCalendar = Calendar.getInstance();
        rollingStartDateCalendar.setTime(rollingStartDate);

        List<IndicatorCalcTimeRangeDto> indicatorCalcTimeRangeDtoList = new ArrayList<>();

        while (true) {

            IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto = new IndicatorCalcTimeRangeDto();
            indicatorCalcTimeRangeDto.setStartDate(DateUtil.DateToString(rollingStartDateCalendar.getTime(), DateConst.YYYY_MM_DD));

            rollingStartDateCalendar.add(Calendar.MONTH, rollingWindow);
            if (rollingStartDateCalendar.getTime().after(DateUtil.StringToDate(endDateStr))) {
                break;
            }
            indicatorCalcTimeRangeDto.setEndDate(DateUtil.DateToString(rollingStartDateCalendar.getTime(), DateConst.YYYY_MM_DD));
            rollingStartDateCalendar.add(Calendar.MONTH, 0-rollingWindow);

            if (StringUtils.isNotEmpty(id)) {
                indicatorCalcTimeRangeDto.setId(id);
            } else {
                indicatorCalcTimeRangeDto.setId(indicatorCalcTimeRangeDto.getStartDate()+"-"+indicatorCalcTimeRangeDto.getEndDate());
            }

            indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Custom);
            indicatorCalcTimeRangeDtoList.add(indicatorCalcTimeRangeDto);

            rollingStartDateCalendar.add(Calendar.MONTH, rollingStep);
        }
        return indicatorCalcTimeRangeDtoList;
    }

    public static List<IndicatorCalcTimeRangeDto> getTimeInterval(List<TimeIntervalDto> timeIntervalDtoList, String minIncept, List<DateValue> validNavList, List<IndexesTradeDateDo> indexesTradeDateDoList) {
        if (timeIntervalDtoList == null || timeIntervalDtoList.size() == 0) {
            return new ArrayList<>();
        }

        List<IndicatorCalcTimeRangeDto> indicatorCalcTimeRangeDtoList = new ArrayList<>();
        for (TimeIntervalDto timeIntervalDto : timeIntervalDtoList) {
            if (StringUtils.isEmpty(timeIntervalDto.getEndDate())) {
                continue;
            }
            if (StringUtils.isEmpty(timeIntervalDto.getStartDate()) && !TimeRange.ROLLING_TIMERANGE.contains(timeIntervalDto.getTimeRange())) {
                continue;
            }

            IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto = new IndicatorCalcTimeRangeDto();
            indicatorCalcTimeRangeDto.setId(timeIntervalDto.getId());
            if (timeIntervalDto.getTimeRange() != null) {
                indicatorCalcTimeRangeDto.setTimeRange(timeIntervalDto.getTimeRange());
            } else {
                indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Custom);
            }

            indicatorCalcTimeRangeDto.setEndDate(timeIntervalDto.getEndDate());
            indicatorCalcTimeRangeDto.setStartDate(timeIntervalDto.getStartDate());

            prepareIndicatorCalcTimeRangeDto(indicatorCalcTimeRangeDto, minIncept, validNavList, indexesTradeDateDoList);

            indicatorCalcTimeRangeDtoList.add(indicatorCalcTimeRangeDto);
        }
        return indicatorCalcTimeRangeDtoList;
    }

    public static List<IndicatorCalcTimeRangeDto> getTrailingInterval(String id, String endDateStr,
                                                                      int trailingNum, int trailingStep) {
        if (trailingStep == 0 || trailingNum == 0) {
            return new ArrayList<>();
        }

        Date rollingStartDate = DateUtil.StringToDate(endDateStr);
        Calendar rollingStartDateCalendar = Calendar.getInstance();
        rollingStartDateCalendar.setTime(rollingStartDate);

        Date rollingEndDate = DateUtil.StringToDate(endDateStr);
        Calendar rollingEndDateCalendar = Calendar.getInstance();
        rollingEndDateCalendar.setTime(rollingEndDate);

        List<IndicatorCalcTimeRangeDto> indicatorCalcTimeRangeDtoList = new ArrayList<>();

        for (int i = 0 ; i < trailingNum ; i++) {
            rollingStartDateCalendar.add(Calendar.MONTH, 0-trailingStep);

            IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto = new IndicatorCalcTimeRangeDto();
            indicatorCalcTimeRangeDto.setId(id);
            indicatorCalcTimeRangeDto.setEndDate(DateUtil.DateToString(rollingEndDateCalendar.getTime(), DateConst.YYYY_MM_DD));
            indicatorCalcTimeRangeDto.setStartDate(DateUtil.DateToString(rollingStartDateCalendar.getTime(), DateConst.YYYY_MM_DD));
            indicatorCalcTimeRangeDto.setTimeRange(TimeRange.Custom);
            indicatorCalcTimeRangeDtoList.add(indicatorCalcTimeRangeDto);
        }
        return indicatorCalcTimeRangeDtoList;
    }

    public static List<IndicatorCalcTimeRangeDto> getDefaultInterval(String endDate, String firstNavDate, String id, List<TimeRange> timeRangeList) {
        List<IndicatorCalcTimeRangeDto> defaultIndicatorCalcTimeRangeDtoList = new ArrayList<>();
        for (TimeRange timeRange : timeRangeList) {
            IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto = new IndicatorCalcTimeRangeDto();
            String startDate = null;
            if (TimeRange.FromSetup == timeRange) {
                startDate = firstNavDate;
            } else {
                startDate = TimeRangeFactory.getInstance().get(timeRange).generateStartDate(endDate, timeRange);
            }
            indicatorCalcTimeRangeDto.setStartDate(startDate);
            indicatorCalcTimeRangeDto.setEndDate(endDate);
            indicatorCalcTimeRangeDto.setTimeRange(timeRange);
            if (StringUtils.isEmpty(id)) {
                indicatorCalcTimeRangeDto.setId("DefaultInterval");
            } else {
                indicatorCalcTimeRangeDto.setId(id);
            }
            defaultIndicatorCalcTimeRangeDtoList.add(indicatorCalcTimeRangeDto);
        }
        return defaultIndicatorCalcTimeRangeDtoList;
    }

    public static List<IndicatorCalcTimeRangeDto> getIndicatorCalcTimeRangeDto(DateIntervalDto dateIntervalDto, List<DateValue> navList, List<IndexesTradeDateDo> indexesTradeDateDoList, String id) {

        List<DateValue> validNavList = new ArrayList<>();
        for (DateValue nav : navList) {
            if (nav.getIsvalid() != null && nav.getIsvalid() == 1) {
                DateValue validNav = new DateValue();
                validNav.setIsvalid(1);
                validNav.setDate(nav.getDate());
                validNav.setValue(nav.getValue());
                validNavList.add(validNav);
            }
        }

        String maxNew = "";
        String minIncept = "";
        if (navList != null && navList.size() > 0) {
            maxNew = navList.get(navList.size() - 1).getDate();
            minIncept = navList.get(0).getDate();
        }

        String endDate = getEndDateByDataPointReq(dateIntervalDto.getEndDate(), maxNew, minIncept);

        String startDate = getDateByDataPointReq(dateIntervalDto.getStartDate(), endDate, maxNew, minIncept, dateIntervalDto.getTimeRange());

        if (startDate != null && startDate.compareTo(minIncept) < 0) {
            startDate = minIncept;
        }

        if (DateIntervalType.DefaultRolling == dateIntervalDto.getDateIntervalType()) {
            List<IndicatorCalcTimeRangeDto> defualtRollingInterval = getDefualtRollingInterval(startDate, endDate,
                    dateIntervalDto.getFrequency(), validNavList, indexesTradeDateDoList, id);
            return defualtRollingInterval;
        } else if (DateIntervalType.CustomRolling == dateIntervalDto.getDateIntervalType()) {
            List<IndicatorCalcTimeRangeDto> customRollingInterval = getCustomRollingInterval(startDate, endDate,
                    dateIntervalDto.getWindowOrNum(), dateIntervalDto.getStep(), dateIntervalDto.getId());
            return customRollingInterval;
        } else if (DateIntervalType.CustomInterval == dateIntervalDto.getDateIntervalType()) {
            List<TimeIntervalDto> timeIntervalDtoList = new ArrayList<>();
            TimeIntervalDto timeIntervalDto = new TimeIntervalDto();
            timeIntervalDto.setId(dateIntervalDto.getId());
            timeIntervalDto.setStartDate(startDate);
            timeIntervalDto.setEndDate(endDate);
            timeIntervalDto.setTimeRange(dateIntervalDto.getTimeRange());
            timeIntervalDtoList.add(timeIntervalDto);
            List<IndicatorCalcTimeRangeDto> customTimeInterval = getTimeInterval(timeIntervalDtoList, minIncept, validNavList, indexesTradeDateDoList);
            return customTimeInterval;
        } else if (DateIntervalType.TrailingInterval == dateIntervalDto.getDateIntervalType()) {
            List<IndicatorCalcTimeRangeDto> trailingInterval = getTrailingInterval(dateIntervalDto.getId(), endDate,
                    dateIntervalDto.getWindowOrNum(), dateIntervalDto.getStep());
            return trailingInterval;
        } else if (DateIntervalType.DefaultInterval == dateIntervalDto.getDateIntervalType()) {
            if (validNavList != null && validNavList.size() > 0) {
                if (StringUtils.isNotEmpty(minIncept)) {
                    List<IndicatorCalcTimeRangeDto> defaultInterval = getDefaultInterval(endDate, minIncept, dateIntervalDto.getId(), TimeRange.TRAILING_DATES);
                    return defaultInterval;
                }
            }
        } else if (DateIntervalType.AllInterval == dateIntervalDto.getDateIntervalType()) {
            if (validNavList != null && validNavList.size() > 0) {
                if (StringUtils.isNotEmpty(minIncept)) {
                    List<IndicatorCalcTimeRangeDto> defaultInterval = getDefaultInterval(endDate, minIncept, dateIntervalDto.getId(), TimeRange.TRAILING_DATES_WITH_TEN_YEARS);
                    return defaultInterval;
                }
            }
        }
        return new ArrayList<>();
    }

    private static void prepareIndicatorCalcTimeRangeDto(IndicatorCalcTimeRangeDto indicatorCalcTimeRangeDto, String minIncept, List<DateValue> validNavList
            , List<IndexesTradeDateDo> indexesTradeDateDoList) {
        TimeRange timeRange = indicatorCalcTimeRangeDto.getTimeRange();
        if (timeRange == TimeRange.Custom) {
            return;
        }

        String endDate = indicatorCalcTimeRangeDto.getEndDate();
        String startDate = null;

        if (timeRange == TimeRange.FromSetup) {
            startDate = minIncept;
        } else if (TimeRange.ROLLING_TIMERANGE.contains(timeRange)) {
            Frequency frequency = null;
            int i = 0;
            if (timeRange == TimeRange.ThisWeek) {
                frequency = Frequency.Weekly;
            } else if (timeRange == TimeRange.ThisMonth) {
                frequency = Frequency.Monthly;
            } else if (timeRange == TimeRange.ThisQuarter) {
                frequency = Frequency.Quarterly;
            } else if (TimeRange.YEAR_ROLLING_TIMERANGE.contains(timeRange)) {
                frequency = Frequency.Annually;
                if (timeRange == TimeRange.Last1RollYear) {
                    i = 1;
                } else if (timeRange == TimeRange.Last2RollYears) {
                    i = 2;
                } else if (timeRange == TimeRange.Last3RollYears) {
                    i = 3;
                } else if (timeRange == TimeRange.Last4RollYears) {
                    i = 4;
                } else if (timeRange == TimeRange.Last5RollYears) {
                    i = 5;
                }
            }
            List<DateValue> dateValues = NavUtil.filterDefaultRollingFrequencyNav(validNavList, indexesTradeDateDoList, frequency, endDate);

            startDate = getRollingStartDate(dateValues, endDate, i);

            if (i > 0) {
                String rollendDate = getRollingStartDate(dateValues, endDate, i-1);
                indicatorCalcTimeRangeDto.setId(indicatorCalcTimeRangeDto.getEndDate());
                indicatorCalcTimeRangeDto.setEndDate(rollendDate);
            }

        } else {
            startDate = TimeRangeFactory.getInstance().get(timeRange).generateStartDate(endDate, timeRange);
        }
        indicatorCalcTimeRangeDto.setStartDate(startDate);
    }

    private static String getRollingStartDate(List<DateValue> dateValues, String endDate, int i) {

        i = i + 1;
        if (dateValues == null || dateValues.size() == 0) {
            return endDate;
        }
        Integer mostLeftLessNav = getMostLeftLessNav(dateValues, endDate);

        if (mostLeftLessNav.compareTo(-1) == 0) {
            return endDate;
        }

        List<DateValue> validDateValues = dateValues.subList(0, mostLeftLessNav + 1);

        if (validDateValues.size() == 0) {
            return endDate;
        }
        if (validDateValues.size() < i) {
            return "";
        }

        return validDateValues.get(validDateValues.size() - i).getDate();
    }

    /**
     *
     * @param dateList   从小到大的净值序列
     * @param date  日期
     * @return  小于等于 date 的 日期最大的净值
     */
    public static Integer getMostLeftLessNav(List<DateValue> dateList, String date) {
        if (dateList == null || dateList.size() == 0) {
            return -1;
        }
        int L = 0;
        int R = dateList.size() - 1;
        int ans = -1;
        while (L <= R) {
            int mid = (L + R) / 2;
            if (dateList.get(mid) !=null && dateList.get(mid).getDate().compareTo(date) < 0) {
                ans = mid;
                L = mid + 1;
            } else {
                R = mid - 1;
            }
        }
        return ans;
    }

    public static String getDateByDataPointReq(String reqDate, String endDate, String maxNew, String minIncept, TimeRange timeRange) {

        if ("maxNew".equals(reqDate)) {
            return maxNew;
        } else if ("minIncept".equals(reqDate) || "incep".equals(reqDate)) {
            return minIncept;
        } else if (timeRange == TimeRange.FromSetup) {
            return minIncept;
        } else if (reqDate != null && ( (reqDate.length() == 4 && reqDate.contains("-")) || "ytd".equals(reqDate))) {
            TimeRange timeRangeByFsOrAs = TimeRange.getTimeRangeByFsOrAsOrS(reqDate);
            if (timeRangeByFsOrAs != null) {
                String startDate = TimeRangeFactory.getInstance().get(timeRangeByFsOrAs).generateStartDate(endDate, timeRangeByFsOrAs);
                return startDate;
            }
        } else if (timeRange != null && timeRange != TimeRange.Custom && !TimeRange.ROLLING_TIMERANGE.contains(timeRange)) {
            String startDate = TimeRangeFactory.getInstance().get(timeRange).generateStartDate(endDate, timeRange);
            return startDate;
        } else if (reqDate != null && reqDate.length() == 10) {
            return reqDate;
        }
        return null;
    }

    public static String getEndDateByDataPointReq(String reqDate, String maxNew, String minIncept) {
        if ("maxNew".equals(reqDate) || "maxNavDate".equals(reqDate)) {
            if (StringUtils.isEmpty(maxNew)) {
                return DateUtil.getTodayDate();
            }
            return maxNew;
        } else if ("minIncept".equals(reqDate) || "incep".equals(reqDate)) {
            return minIncept;
        } else if (reqDate != null && reqDate.length() == 10) {
            return reqDate;
        } else {
            if (StringUtils.isEmpty(maxNew)) {
                return DateUtil.getTodayDate();
            }
            return maxNew;
        }
    }
}
