package com.bonus.common.core.utils;

import org.apache.commons.lang3.ObjectUtils;

import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * 日期工具类，提供常见的日期处理方法。
 * 作者: bonus
 */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {

    // 常见日期格式
    public static final String YYYY = "yyyy";
    public static final String YYYY_MM = "yyyy-MM";
    public static final String YYYY_MM_DD = "yyyy-MM-dd";
    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";

    private static final String[] PARSE_PATTERNS = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

    /**
     * 获取当前 Date 型日期
     *
     * @return 当前日期
     */
    public static Date getNowDate() {
        return new Date();
    }

    /**
     * 获取当前日期，格式为 yyyy-MM-dd
     *
     * @return 当前日期的字符串
     */
    public static String getDate() {
        return dateTimeNow(YYYY_MM_DD);
    }

    /**
     * 获取当前时间，格式为 yyyy-MM-dd HH:mm:ss
     *
     * @return 当前时间的字符串
     */
    public static String getTime() {
        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
    }

    /**
     * 获取当前年份
     *
     * @return 当前年份的字符串
     */
    public static String getCurrentYear() {
        return String.valueOf(LocalDate.now().getYear());
    }

    /**
     * 获取当前月份
     *
     * @return 当前月份的字符串
     */
    public static String getCurrentMonth() {
        return String.format("%02d", LocalDate.now().getMonthValue());
    }

    /**
     * 获取当前日期中的日
     *
     * @return 当前日的字符串
     */
    public static String getCurrentDay() {
        return String.format("%02d", LocalDate.now().getDayOfMonth());
    }

    /**
     * 获取当前日期时间，格式为 yyyyMMddHHmmss
     *
     * @return 当前日期时间的字符串
     */
    public static String dateTimeNow() {
        return dateTimeNow(YYYYMMDDHHMMSS);
    }

    /**
     * 根据指定格式获取当前日期时间
     *
     * @param format 日期格式
     * @return 当前日期时间的字符串
     */
    public static String dateTimeNow(final String format) {
        return parseDateToStr(format, new Date());
    }

    /**
     * 将 Date 类型日期转换为指定格式的字符串
     *
     * @param format 日期格式
     * @param date   日期
     * @return 格式化后的日期字符串
     */
    public static String parseDateToStr(final String format, final Date date) {
        return new SimpleDateFormat(format).format(date);
    }

    /**
     * 将字符串解析为 Date 类型日期
     *
     * @param format 日期格式
     * @param ts     日期字符串
     * @return 解析后的日期
     */
    public static Date dateTime(final String format, final String ts) {
        try {
            return new SimpleDateFormat(format).parse(ts);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 计算两个日期之间的时间差，返回格式为 X天X小时X分钟
     *
     * @param endDate   结束日期
     * @param startDate 开始日期
     * @param separator 自定义拼接符
     * @return 时间差字符串
     */
    public static String timeDistance(Date endDate, Date startDate, String separator) {
        long duration = endDate.getTime() - startDate.getTime();
        long days = TimeUnit.MILLISECONDS.toDays(duration);
        long hours = TimeUnit.MILLISECONDS.toHours(duration) % 24;
        long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) % 60;
        if (ObjectUtils.isEmpty(separator)) {
            return days + "天" + hours + "小时" + minutes + "分钟";
        }
        return days + separator + hours + separator + minutes;
    }

    /**
     * 将 LocalDateTime 转换为 Date
     *
     * @param localDateTime LocalDateTime 对象
     * @return 转换后的 Date 对象
     */
    public static Date toDate(LocalDateTime localDateTime) {
        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }

    /**
     * 将 LocalDate 转换为 Date
     *
     * @param localDate LocalDate 对象
     * @return 转换后的 Date 对象
     */
    public static Date toDate(LocalDate localDate) {
        LocalDateTime localDateTime = localDate.atTime(0, 0, 0);
        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
        return Date.from(zdt.toInstant());
    }

    /**
     * 将 Date 转换为 LocalDateTime
     *
     * @param date Date 对象
     * @return 转换后的 LocalDateTime 对象
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        Instant instant = date.toInstant();
        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    }

    /**
     * 将 Date 转换为 LocalDate
     *
     * @param date Date 对象
     * @return 转换后的 LocalDate 对象
     */
    public static LocalDate toLocalDate(Date date) {
        Instant instant = date.toInstant();
        return instant.atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * 将 Date 转换为 LocalTime
     *
     * @param date Date 对象
     * @return 转换后的 LocalTime 对象
     */
    public static LocalTime toLocalTime(Date date) {
        Instant instant = date.toInstant();
        return instant.atZone(ZoneId.systemDefault()).toLocalTime();
    }

    /**
     * 获取服务器启动时间
     *
     * @return 服务器启动时间
     */
    public static Date getServerStartDate() {
        long startTime = ManagementFactory.getRuntimeMXBean().getStartTime();
        return new Date(startTime);
    }

    /**
     * 获取当前日期路径，格式为 yyyy/MM/dd
     *
     * @return 当前日期路径字符串
     */
    public static String datePath() {
        return parseDateToStr("yyyy/MM/dd", new Date());
    }

    /**
     * 获取当前日期路径，格式为 yyyyMMdd
     *
     * @return 当前日期路径字符串
     */
    public static String dateTime() {
        return parseDateToStr("yyyyMMdd", new Date());
    }

    /**
     * 获取两个日期之间的天数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 两个日期之间的天数
     */
    public static long daysBetween(Date startDate, Date endDate) {
        long diffInMillis = endDate.getTime() - startDate.getTime();
        return TimeUnit.DAYS.convert(diffInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取两个日期之间的小时数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 两个日期之间的小时数
     */
    public static long hoursBetween(Date startDate, Date endDate) {
        long diffInMillis = endDate.getTime() - startDate.getTime();
        return TimeUnit.HOURS.convert(diffInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取两个日期之间的分钟数
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     * @return 两个日期之间的分钟数
     */
    public static long minutesBetween(Date startDate, Date endDate) {
        long diffInMillis = endDate.getTime() - startDate.getTime();
        return TimeUnit.MINUTES.convert(diffInMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * 增加指定天数到日期
     *
     * @param date 日期
     * @param days 天数
     * @return 增加天数后的日期
     */
    public static Date addDays(Date date, int days) {
        LocalDateTime localDateTime = toLocalDateTime(date);
        localDateTime = localDateTime.plusDays(days);
        return toDate(localDateTime);
    }

    /**
     * 增加指定小时数到日期
     *
     * @param date  日期
     * @param hours 小时数
     * @return 增加小时数后的日期
     */
    public static Date addHours(Date date, int hours) {
        LocalDateTime localDateTime = toLocalDateTime(date);
        localDateTime = localDateTime.plusHours(hours);
        return toDate(localDateTime);
    }

    /**
     * 日期型字符串转化为日期 格式
     */
    public static Date parseDate(Object str) {
        if (str == null) {
            return null;
        }
        try {
            return parseDate(str.toString(), PARSE_PATTERNS);
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * 增加指定分钟数到日期
     *
     * @param date    日期
     * @param minutes 分钟数
     * @return 增加分钟数后的日期
     */
    public static Date addMinutes(Date date, int minutes) {
        LocalDateTime localDateTime = toLocalDateTime(date);
        localDateTime = localDateTime.plusMinutes(minutes);
        return toDate(localDateTime);
    }

    /**
     * 判断一个日期是否在另一个日期之前
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return 如果日期1在日期2之前，返回 true；否则返回 false
     */
    public static boolean isBefore(Date date1, Date date2) {
        return date1.before(date2);
    }

    /**
     * 判断一个日期是否在另一个日期之后
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return 如果日期1在日期2之后，返回 true；否则返回 false
     */
    public static boolean isAfter(Date date1, Date date2) {
        return date1.after(date2);
    }

    /**
     * 判断两个日期是否相等
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return 如果两个日期相等，返回 true；否则返回 false
     */
    public static boolean isEqual(Date date1, Date date2) {
        return date1.equals(date2);
    }

    /**
     * 获取当前时间的 Unix 时间戳
     *
     * @return 当前时间的 Unix 时间戳
     */
    public static long getUnixTimestamp() {
        return System.currentTimeMillis() / 1000L;
    }

    /**
     * 将 Unix 时间戳转换为 Date
     *
     * @param timestamp Unix 时间戳
     * @return 转换后的 Date 对象
     */
    public static Date fromUnixTimestamp(long timestamp) {
        return new Date(timestamp * 1000L);
    }

    /**
     * 将 Date 转换为 Unix 时间戳
     *
     * @param date Date 对象
     * @return 转换后的 Unix 时间戳
     */
    public static long toUnixTimestamp(Date date) {
        return date.getTime() / 1000L;
    }

    /**
     * 获取当前日期时间，格式为 yyyy-MM-dd'T'HH:mm:ss.SSSZ
     *
     * @return 当前日期时间的字符串
     */
    public static String getISO8601Timestamp() {
        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.now());
    }

    /**
     * 将 ISO8601 格式的字符串转换为 Date
     *
     * @param iso8601Timestamp ISO8601 格式的日期字符串
     * @return 转换后的 Date 对象
     */
    public static Date fromISO8601Timestamp(String iso8601Timestamp) {
        OffsetDateTime odt = OffsetDateTime.parse(iso8601Timestamp);
        return Date.from(odt.toInstant());
    }

    /**
     * 获取本周的开始时间
     *
     * @return 本周的开始时间
     */
    public static Date getStartOfWeek() {
        LocalDate now = LocalDate.now();
        LocalDate startOfWeek = now.with(DayOfWeek.MONDAY);
        return toDate(startOfWeek);
    }

    /**
     * 获取本周的结束时间
     *
     * @return 本周的结束时间
     */
    public static Date getEndOfWeek() {
        LocalDate now = LocalDate.now();
        LocalDate endOfWeek = now.with(DayOfWeek.SUNDAY);
        return toDate(endOfWeek.atTime(LocalTime.MAX));
    }

    /**
     * 获取当前日期是当年的第几周
     *
     * @return 当前日期是当年的第几周
     */
    public static int getWeekOfYear() {
        LocalDate now = LocalDate.now();
        return now.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
    }

    /**
     * 获取指定日期前几天的日期
     *
     * @param date 日期
     * @param days 天数
     * @return 指定日期前几天的日期
     */
    public static Date getDateBefore(Date date, int days) {
        LocalDateTime localDateTime = toLocalDateTime(date);
        localDateTime = localDateTime.minusDays(days);
        return toDate(localDateTime);
    }

    /**
     * 获取指定日期后几天的日期
     *
     * @param date 日期
     * @param days 天数
     * @return 指定日期后几天的日期
     */
    public static Date getDateAfter(Date date, int days) {
        LocalDateTime localDateTime = toLocalDateTime(date);
        localDateTime = localDateTime.plusDays(days);
        return toDate(localDateTime);
    }

    /**
     * 根据输入的日 返回月/日
     *
     * @param day       日
     * @param separator 分隔符
     * @return 月/日
     */
    public static String getMonthOrDay(int day, String separator) {
        LocalDate today = LocalDate.now();
        if (day < 1 || day > today.lengthOfMonth()) {
            return null;
        } else {
            if (ObjectUtils.isEmpty(separator)) {
                return String.format("%02d", LocalDate.now().getMonthValue()) + "月" + day;
            } else {
                return String.format("%02d", LocalDate.now().getMonthValue()) + separator + day;
            }

        }
    }

    /**
     * 将日期字符串转换为 ISO 8601 格式
     *
     * @param dateStr 输入的日期字符串
     * @param format  输入日期的格式
     * @return ISO 8601 格式的日期字符串
     */
    public static String convertToISO8601(String dateStr, String format) {
        DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern(format);
        DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_DATE_TIME;
        LocalDateTime dateTime = LocalDateTime.parse(dateStr, inputFormatter);
        return dateTime.format(isoFormatter);
    }

    /**
     * 获取两个时间之间的相差秒数
     *
     * @param startTimeStr 开始时间的字符串
     * @param endTimeStr   结束时间的字符串
     * @param format       时间格式
     * @return 相差的秒数
     */
    public static long getSecondsBetween(String startTimeStr, String endTimeStr, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);

        LocalDateTime startTime = LocalDateTime.parse(startTimeStr, formatter);
        LocalDateTime endTime = LocalDateTime.parse(endTimeStr, formatter);

        Duration duration = Duration.between(startTime, endTime);
        return duration.getSeconds();
    }

    /**
     * 根据输入日期字符串返回本周的周一和周日的日期
     *
     * @param dateStr 日期字符串
     * @param format  日期格式
     * @return 包含本周一和周日日期的数组
     */
    public static String[] getWeekStartAndEndDate(String dateStr, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        LocalDate date = LocalDate.parse(dateStr, formatter);

        // 获取本周一
        LocalDate monday = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        // 获取本周日
        LocalDate sunday = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
        return new String[] {
                monday.format(formatter),
                sunday.format(formatter)
        };
    }

    /**
     * 根据输入日期字符串返回本周的开始时间和结束时间
     *
     * @param dateStr 日期字符串
     * @param format  日期格式
     * @return 包含本周开始时间和结束时间的数组
     */
    public static String[] getWeekStartAndEndDateTime(String dateStr, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        LocalDate date = LocalDate.parse(dateStr, formatter);

        // 获取本周一的开始时间
        LocalDateTime startOfWeek = date.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                .atStartOfDay();

        // 获取本周日的结束时间
        LocalDateTime endOfWeek = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
                .atTime(23, 59, 59);

        return new String[] {
                startOfWeek.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                endOfWeek.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        };
    }

    /**
     * 根据输入日期字符串返回该日期属于本月的第几周
     *
     * @param dateStr 日期字符串
     * @param format  日期格式
     * @return 该日期属于本月的第几周
     */
    public static int getWeekOfMonth(String dateStr, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        LocalDate date = LocalDate.parse(dateStr, formatter);

        // 获取周字段，周一为一周的开始
        WeekFields weekFields = WeekFields.of(Locale.getDefault());
        return date.get(weekFields.weekOfMonth());
    }

    /**
     * 获取从当前日期往前推 n 天的所有日期，并以字符串形式返回
     *
     * @param n 天数
     * @param format 日期格式
     * @return 从当前日期往前推 n 天的所有日期的字符串列表
     */
    public static List<String> getDatesBefore(int n, String format) {
        List<String> dates = new ArrayList<>();
        LocalDate today = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);

        for (int i = 0; i < n; i++) {
            LocalDate date = today.minusDays(i);
            dates.add(date.format(formatter));
        }

        return dates;
    }

    /**
     * 获取从当前日期往后推 n 天的所有日期，并以字符串形式返回
     *
     * @param n 天数
     * @param format 日期格式
     * @return 从当前日期往后推 n 天的所有日期的字符串列表
     */
    public static List<String> getDatesAfter(int n, String format) {
        List<String> dates = new ArrayList<>();
        LocalDate today = LocalDate.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);

        for (int i = 0; i < n; i++) {
            LocalDate date = today.plusDays(i);
            dates.add(date.format(formatter));
        }

        return dates;
    }

    /**
     * 根据输入日期字符串获取从指定日期往后推 n 天的所有日期，并以字符串形式返回
     *
     * @param startDateStr 起始日期字符串
     * @param n            天数
     * @param format       输入日期的格式
     * @return 从指定日期往后推 n 天的所有日期的字符串列表
     */
    public static List<String> getDatesAfter(String startDateStr, int n, String format) {
        List<String> dates = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        LocalDate startDate = LocalDate.parse(startDateStr, formatter);

        for (int i = 0; i < n; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(formatter));
        }

        return dates;
    }

    /**
     * 根据输入日期字符串获取从指定日期往前推 n 天的所有日期，并以字符串形式返回
     *
     * @param startDateStr 起始日期字符串
     * @param n            天数
     * @param format       日期格式
     * @return 从指定日期往前推 n 天的所有日期的字符串列表
     */
    public static List<String> getDatesBefore(String startDateStr, int n, String format) {
        List<String> dates = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        LocalDate startDate = LocalDate.parse(startDateStr, formatter);

        for (int i = 0; i < n; i++) {
            LocalDate date = startDate.minusDays(i);
            dates.add(date.format(formatter));
        }

        return dates;
    }
}
