package com.sonic.common.utils;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

/**
 * LocalDateTime 时间时区转换统一工具类
 * @Author code
 * @Date 2020/8/11 17:30
 * @Version 1.0
 */
@Slf4j
public class DateConvertUtils {

    /** 系统使用的时区 (默认为 CTT 中国上海时区) yml配置：shortZoneId:system  初始化是在: DefaultWebMvcConfig类中*/
    public static String systemShortZoneId = "CTT";

    /** 需要转换的时区 (默认转换为 PST美国洛杉矶时区) yml配置：shortZoneId:convert 初始化是在: DefaultWebMvcConfig类中*/
    public static String convertShortZoneId = "PST";

    /**
     * 获取当前系统使用的时区ID
     * @return
     */
    public static String getSystemZoneId() {
        return ZoneId.SHORT_IDS.get(systemShortZoneId);
    }

    /**
     * 获取需要转换使用的时区ID
     * @return
     */
    public static String getConvertZoneId() {
        return ZoneId.SHORT_IDS.get(convertShortZoneId);
    }

    /**
     * 获取默认转换时区（convertShortZoneId）后的时间
     * @return
     */
    public static LocalDateTime getDefaultZoneTime() {
        return LocalDateTime.now(Clock.system(ZoneId.of(getConvertZoneId())));
    }

    /**
     * 根据传入的时间转换成指定配置时区（convertShortZoneId）后的时间
     * @param localDateTime
     * @return
     */
    public static LocalDateTime getZoneTime(LocalDateTime localDateTime) {
        ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(getSystemZoneId())).withZoneSameInstant(ZoneId.of(getConvertZoneId()));
        return zonedDateTime.toLocalDateTime();
    }

    /**
     * 将指定时间的指定时区转换为目标时区的时间
     * @param localDateTime 时间
     * @param fromZone      指定时区
     * @param toZone        目标时区
     * @return
     */
    public static LocalDateTime toZone(LocalDateTime localDateTime, String fromZone, String toZone) {
        return toZone(localDateTime, ZoneId.of(fromZone), ZoneId.of(toZone));
    }

    /**
     * 将指定时间的指定时区转换为目标时区的时间
     * @param localDateTime 时间
     * @param fromZone      指定时区
     * @param toZone        目标时区
     * @return
     */
    public static LocalDateTime toZone(LocalDateTime localDateTime, ZoneId fromZone, ZoneId toZone) {
        final ZonedDateTime zonedTime = localDateTime.atZone(fromZone);
        final ZonedDateTime converted = zonedTime.withZoneSameInstant(toZone);
        return converted.toLocalDateTime();
    }

    /**
     * 获取指定配置时区（convertShortZoneId）本周周一的 字符串（yyyy-MM-dd）时间
     * @return
     */
    public static String getMondayOfThisWeek() {
        LocalDateTime monday = getDefaultZoneTime().with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(1);
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = dateTimeFormatter.format(monday);
        return date;
    }

    /**
     * 获取指定配置时区（convertShortZoneId）的周一的 LocalDateTime 时间
     * @return
     */
    public static LocalDateTime getMondayOfThisWeekToTime() {
        LocalDateTime monday = getDefaultZoneTime().with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
        return LocalDateTime.of(monday.toLocalDate(), LocalTime.MIN);
    }

    /**
     * 获取指定配置时区（convertShortZoneId）且 指定周的 周一的 LocalDateTime 时间
     * @return
     */
    public static LocalDateTime getMondayOfWeekToTime(WeekEnum weekEnum) {
        LocalDateTime monday = getDefaultZoneTime().with(TemporalAdjusters.previous(DayOfWeek.SUNDAY));
        return LocalDateTime.of(monday.plusDays(weekEnum.day).toLocalDate(), LocalTime.MIN);
    }

    public static void main(String[] args) {

        System.out.println(getZoneCurrentDayMinTime(LocalDateTime.now()));

        System.out.println(getZoneCurrentMonday(LocalDateTime.now()));

//        System.out.println(getMondayOfThisWeekToTime());
        System.out.println(getDefaultZoneTime());
        System.out.println(getMondayOfThisWeek());
        System.out.println(getLastMonday(LocalDateTime.now()));
        System.out.println(getLocalDateTimeByDayOfWeek(DayOfWeek.WEDNESDAY));
        System.out.println(getLocalDateTimeByDayOfWeek(DayOfWeek.SATURDAY));
        System.out.println(getLocalDateTimeByDayOfWeek(DayOfWeek.SUNDAY));
    }

    /**
     * 获取指定配置时区（convertShortZoneId）当前的年数（注意跨年的问题:已解决）
     * @return
     */
    public static int getWeekDeductionYear() {
        LocalDateTime now = getDefaultZoneTime();
        WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY,4);
        int year = now.get(weekFields.weekBasedYear());
        return year;
    }

    /**
     * 获取指定配置时区（convertShortZoneId）当前年中的周数（注意跨年的问题:已解决）
     * @return
     */
    public static int getWeekDeductionYearWeek() {
        LocalDateTime now = getDefaultZoneTime();
        WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY,4);
        int week = now.get(weekFields.weekOfWeekBasedYear());
        return week;
    }

    /**
     * 根据传入的时间不做时区转换进行格式转换(yyyy-MM-dd)
     * @param localDateTime
     * @return
     */
    public static String noTimeZoneChangeFormat(LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = dateTimeFormatter.format(localDateTime);
        return date;
    }

    /**
     * 根据传入的时间不做时区转换进行格式转换(yyyy-MM-dd)
     * @param localDateTime
     * @param pattern
     * @return
     */
    public static String noTimeZoneChangeFormat(LocalDateTime localDateTime, String pattern) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
        String date = dateTimeFormatter.format(localDateTime);
        return date;
    }

    /**
     * 根据传入的时间不做时区转换进行格式转换(yyyy-MM-dd)
     * @param localDate
     * @return
     */
    public static String noTimeZoneChangeFormat(LocalDate localDate) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = localDate.format(dateTimeFormatter);
        return date;
    }

    /**
     * 根据传入的时间不做时区转换进行格式转换(yyyy-MM-dd)
     * @param date
     * @return
     */
    public static String noTimeZoneChangeFormat(Date date) {
        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd");
        String result = dateTimeFormatter.format(date);
        return result;
    }

    /**
     * 将传入的时间 转换成指定配置时区（convertShortZoneId）格式的字符数据
     * @param localDateTime
     * @param pattern
     * @return
     */
    public static String formatZone(LocalDateTime localDateTime, String pattern) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
        String date = dateTimeFormatter.format(getZoneTime(localDateTime));
        return date;
    }

    /**
     * 将传入的时间 转换成指定配置时区（convertShortZoneId）指定格式（yyyy-MM-dd）的字符数据
     * @param localDateTime
     * @return
     */
    public static String formatZone(LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = dateTimeFormatter.format(getZoneTime(localDateTime));
        return date;
    }

    /**
     * 获取指定配置时区（convertShortZoneId）的当前时间，并转换成指定格式（yyyy-MM-dd）的字符数据
     * @return
     */
    public static String format() {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = dateTimeFormatter.format(getDefaultZoneTime());
        return date;
    }

    /**
     * 将传入的时间转换成 转换成指定格式（yyyy-MM-dd）的字符数据
     * @param localDateTime
     * @return
     */
    @Deprecated
    public static String format(LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String date = dateTimeFormatter.format(localDateTime);
        return date;
    }

    /**
     * 将传入的时间转换成指定配置时区（convertShortZoneId）的 数字时间（yyyyMMdd）
     * @param localDateTime
     * @return
     */
    public static Integer formatZoneToInt(LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = dateTimeFormatter.format(getZoneTime(localDateTime));
        return Integer.valueOf(date);
    }

    /**
     * 将传入的时间转换成 数字时间（yyyyMMdd）
     * @param localDateTime
     * @return
     */
    public static Integer formatToInt(LocalDateTime localDateTime) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        String date = dateTimeFormatter.format(localDateTime);
        return Integer.valueOf(date);
    }

    /**
     * 获取指定配置时区（convertShortZoneId）的当前时间的最小时间
     * @return
     */
    public static LocalDateTime getZoneDateMinTime(){
        LocalDateTime now = getDefaultZoneTime().with(LocalTime.MIN);
        return now;
    }

    /**
     * 获取指定配置时区（convertShortZoneId）的当前时间的最大时间
     * @return
     */
    public static LocalDateTime getZoneDateMaxTime(){
        LocalDateTime now = getDefaultZoneTime().with(LocalTime.MAX);
        return now;
    }

    /**
     * 获取传入当天的时间获取当天在转换时区后的开始时间
     * @param localDateTime 传入配置的时区的时间
     * @return
     */
    public static LocalDateTime getZoneCurrentDayMinTime(LocalDateTime localDateTime) {
        LocalDateTime with = getZoneTime(localDateTime).with(LocalTime.MIN);
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }

    /**
     * 获取上周 周一的开始时间
     * @param localDateTime 传入配置的时区的时间
     * @return
     */
    public static LocalDateTime getLastMonday(LocalDateTime localDateTime) {
        LocalDateTime with = localDateTime.toLocalDate().minusWeeks(1L).with(DayOfWeek.MONDAY).atStartOfDay();
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }


    /**
     * 获取上周 周一的开始时间
     * @param localDateTime 传入配置的时区的时间
     * @return
     */
    public static LocalDateTime getZoneLastMonday(LocalDateTime localDateTime) {
        LocalDateTime with = localDateTime.toLocalDate().minusWeeks(1L).with(DayOfWeek.MONDAY).atStartOfDay();
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }

    /**
     * 获取本周 周一的开始时间
     * @param localDateTime 传入配置的时区的时间
     * @return
     */
    public static LocalDateTime getZoneCurrentMonday(LocalDateTime localDateTime) {
        LocalDateTime with = localDateTime.toLocalDate().with(DayOfWeek.MONDAY).atStartOfDay();
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }

    /**
     * 获取本周指定周几的开始时间
     * @param dayOfWeek
     * @return
     */
    public static LocalDateTime getLocalDateTimeByDayOfWeek(DayOfWeek dayOfWeek){
        LocalDateTime with = getDefaultZoneTime().toLocalDate().with(dayOfWeek).atStartOfDay();
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }


    /**
     * 获取上周 周日的结束时间
     * @param localDateTime 传入配置的时区的时间
     * @return
     */
    public static LocalDateTime getLastSunday(LocalDateTime localDateTime) {
        LocalDateTime with = localDateTime.toLocalDate().minusWeeks(1L).with(DayOfWeek.SUNDAY).plusDays(1).atStartOfDay();
        LocalDateTime toZoneTime = toZone(with, ZoneId.SHORT_IDS.get(convertShortZoneId), ZoneId.SHORT_IDS.get(systemShortZoneId));
        return toZoneTime;
    }

    /**
      * 获取两个时间相差的秒数
      * @param startTime
      * @param endTime
      * @return
      */
    public static Long getTimeDifference(LocalDateTime startTime, LocalDateTime endTime) {
        Duration duration = Duration.between(startTime, endTime);
        return duration.getSeconds();
    }

    /**
     * 获取指定时间的时间戳（单位：秒）
     * @param localDateTime
     * @return
     */
    public static Long getTimeSeconds(LocalDateTime localDateTime) {
        Long seconds = localDateTime.toEpochSecond(ZoneOffset.of("+8"));
        return seconds;
    }

    /**
      * 根据传入时区的两个时间 获取两个日期相差的周数
      *
      * @param startDate - 开始时间（需要自己转换时区）
      * @param endDate - 结束时间（需要自己转换时区）
      * @return
      */
    public static int getWeeksBetweenTime(LocalDateTime startDate, LocalDateTime endDate) {
        Calendar dateFrom = GregorianCalendar.from(ZonedDateTime.of(startDate, ZoneId.systemDefault()));
        //设置日历
        Calendar dateTo = GregorianCalendar.from(ZonedDateTime.of(endDate, ZoneId.systemDefault()));
        //获取年份
        int yearFrom = dateFrom.get(Calendar.YEAR);
        int yearTo = dateTo.get(Calendar.YEAR);
        //获取日期所在年的周数
        int weekOfYearFrom = dateFrom.get(Calendar.WEEK_OF_YEAR);
        if (weekOfYearFrom == 1 && dateFrom.get(Calendar.MONTH) == Calendar.DECEMBER) {
            //腊月最后几天的周数可能为1
            weekOfYearFrom = getWeeksInYear(yearFrom, true);
        }
        int weekOfYearTo = dateTo.get(Calendar.WEEK_OF_YEAR);
        if (weekOfYearTo == 1 && dateTo.get(Calendar.MONTH) == Calendar.DECEMBER) {
            weekOfYearTo = getWeeksInYear(yearTo, true);
        }
        //同年直接返回
        if (yearFrom == yearTo) {
            return weekOfYearTo - weekOfYearFrom;
        }
        //不同年：
        int betweenWeeks = weekOfYearTo;
        int betweenYears = yearTo - yearFrom;
        for (int i = 1; i <= betweenYears ; i++) {
            betweenWeeks += getWeeksInYear(yearFrom+i, false);
        }
        betweenWeeks -= weekOfYearFrom;
        return betweenWeeks;
    }

    /**
     * 获取 某 年共有几周,
     * @param year - 年份
     * @param containLastDays - 腊月最后几天不满一周的，是否算为一周
     * @return
     */
    public static int getWeeksInYear(int year, boolean containLastDays){
        Calendar calendar = Calendar.getInstance();
        calendar.set(year, Calendar.DECEMBER,31);
        int i = calendar.get(Calendar.WEEK_OF_YEAR);
        if(i == 1 && containLastDays){
            i = 53;
        }
        if(i != 53){
            i = 52;
        }
        return i;
    }

    /**
     * 获取传入时间获取传入时间当前所在周的周一的时间
     * @param localDateTime
     * @return
     */
    public static LocalDateTime getCurrentMonday(LocalDateTime localDateTime) {
        return localDateTime.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(1).with(LocalTime.MIN);
    }


//    /**
//     * 获取传入时间的上周周一的日期时间
//     * @param localDateTime
//     * @return
//     */
//    public static LocalDate getLastMonday(LocalDateTime localDateTime) {
//        LocalDate now = localDateTime.toLocalDate();
//        LocalDate todayOfLastWeek = now.minusDays(7);
//        LocalDate monday = todayOfLastWeek.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
//        return monday;
//    }

    /**
     * 根据配置的时区 和指定周枚举值 获取周的所有日期时间
     * @return
     */
    public static List<AllDaysOfTheWeekVo> getAllDaysOfWeekByShortZoneId(WeekEnum weekEnum) {
        if(weekEnum == null) {
            return Lists.newArrayList();
        }
        //根据时区来进行判断 如果是美国的时区的话 需要减去一天，其他时区暂时不做扣减，后续需要进行扣减的时间需要在 AllDaysOfTheWeekMinusDaysEnum 枚举中进行配置
        return getAllDaysOfWeek(weekEnum, AllDaysOfTheWeekMinusDaysEnum.getMinusDays(convertShortZoneId));
    }

    /**
     * 获取指定周内的所有日期时间
     * @param weekEnum
     * @param minusDays
     * @return
     */
    private static List<AllDaysOfTheWeekVo> getAllDaysOfWeek(WeekEnum weekEnum, int minusDays) {
        List<LocalDateTime> days = Lists.newArrayList();
        days.add(getDefaultZoneTime().with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(weekEnum.day).plusDays(1).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(6).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(5).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(4).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(3).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(2).minusDays(minusDays).with(LocalTime.MIN));
        days.add(getDefaultZoneTime().with(TemporalAdjusters.next(DayOfWeek.MONDAY)).plusDays(weekEnum.day).minusDays(1).minusDays(minusDays).with(LocalTime.MIN));
        List<AllDaysOfTheWeekVo> voList = Lists.newArrayList();
        days.forEach(day -> {
            AllDaysOfTheWeekVo vo = new AllDaysOfTheWeekVo();
            vo.setIntDayTime(formatToInt(day));
            vo.setDayTime(day);
            voList.add(vo);
        });
        return voList;
    }


    /**
     * 获取指定年、周数的日期 (参考：https://blog.csdn.net/weixin_44919928/article/details/100008249)
     * @param year
     * @param num
     * @param dayOfWeek(MONDAY TUESDAY)
     * @return
     */
    public static LocalDate getDateByYearAndWeekNumAndDayOfWeekParam(Integer year, Integer num, DayOfWeek dayOfWeek) {
        //周数小于10在前面补个0
        String numStr = num < 10 ? "0" + String.valueOf(num) : String.valueOf(num);
        //2019-W01-01获取第一周的周一日期，2019-W02-07获取第二周的周日日期
        String weekDate = String.format("%s-W%s-%s", year, numStr, dayOfWeek.getValue());
        return LocalDate.parse(weekDate, DateTimeFormatter.ISO_WEEK_DATE);
    }

    /**
     * 周类型枚举
     */
    public enum WeekEnum {
        /**
         * 上周、本周、下周
         */
        PREVIOUS_WEEK(-6),
        THIS_WEEK(1),
        NEXT_WEEK(8),
        ;

        int day;

        WeekEnum(int day) {
            this.day = day;
        }
    }

    public enum AllDaysOfTheWeekMinusDaysEnum {
        /**
         * PST 美国时间的周一是 中国时间的周日
         * CTT 中国时间
         * */
        PST(1),
        CTT(0),
        ;

        int minusDays;

        AllDaysOfTheWeekMinusDaysEnum(int minusDays) {
            this.minusDays = minusDays;
        }

        public static int getMinusDays(String shortZoneId) {
            for (AllDaysOfTheWeekMinusDaysEnum allDaysOfTheWeekMinusDaysEnum : AllDaysOfTheWeekMinusDaysEnum.values()) {
                if(allDaysOfTheWeekMinusDaysEnum.name().equals(shortZoneId)) {
                    return allDaysOfTheWeekMinusDaysEnum.minusDays;
                }
            }
            return 0;
        }
    }

    /**
     * 周的所有日期时间对象
     */
    public static class AllDaysOfTheWeekVo {
        /**
         * 数字时间 yyyyMMdd
         */
        private Integer intDayTime;
        private LocalDateTime dayTime;

        public Integer getIntDayTime() {
            return intDayTime;
        }

        public void setIntDayTime(Integer intDayTime) {
            this.intDayTime = intDayTime;
        }

        public LocalDateTime getDayTime() {
            return dayTime;
        }

        public void setDayTime(LocalDateTime dayTime) {
            this.dayTime = dayTime;
        }
    }
}
