package nckd.jxccl.base.common.utils;

import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* 日期时间工具类
* 提供日期时间常用操作，包括转换、格式化、计算、范围处理等功能
* @author W.Y.C
* @date 2025/7/8 14:53
* @version 1.0
*/
public class DateUtil {

    public static final String NORM_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    public static final String NORM_DATE_PATTERN = "yyyy-MM-dd";
    public static final String NORM_TIME_PATTERN = "HH:mm:ss";
    private static final DateTimeFormatter NORM_DATETIME_FORMAT = DateTimeFormatter.ofPattern(NORM_DATETIME_PATTERN);
    private static final DateTimeFormatter NORM_DATE_FORMAT = DateTimeFormatter.ofPattern(NORM_DATE_PATTERN);
    private static final DateTimeFormatter NORM_TIME_FORMAT = DateTimeFormatter.ofPattern(NORM_TIME_PATTERN);

    // ==================== 基础方法 ====================

    /**
     * 获取当前日期时间
     *
     * @return 当前日期时间
     */
    public static LocalDateTime now() {
        return LocalDateTime.now();
    }

    /**
     * 获取当前日期时间
     *
     * @return 当前日期时间
     */
    public static Date nowDate() {
        return toDate(LocalDateTime.now());
    }

    /**
     * 转换为Date对象
     *
     * @param dateTime LocalDateTime对象
     * @return 对应的Date对象
     */
    public static Date toDate(LocalDateTime dateTime) {
        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 转换为Date对象
     *
     * @param localDate LocalDate
     * @return 对应的Date对象
     */
    public static Date toDate(LocalDate localDate) {
        return  Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

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

    // ==================== 格式化与解析 ====================

    /**
     * 格式化日期时间（指定模式）
     *
     * @param dateTime 日期时间对象
     * @param pattern  日期格式模式
     * @return 格式化后的字符串
     */
    public static String format(LocalDateTime dateTime, String pattern) {
        return dateTime.format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 格式化日期时间（默认格式：yyyy-MM-dd HH:mm:ss）
     *
     * @param dateTime 日期时间对象
     * @return 格式化后的字符串
     */
    public static String format(LocalDateTime dateTime) {
        return dateTime.format(NORM_DATETIME_FORMAT);
    }

    /**
     * 格式化日期时间（Date对象版本）
     *
     * @param date    Date对象
     * @param pattern 日期格式模式
     * @return 格式化后的字符串
     */
    public static String format(Date date, String pattern) {
        return format(toLocalDateTime(date), pattern);
    }

    /**
     * 解析日期时间（指定模式）
     *
     * @param dateStr 日期时间字符串
     * @param pattern 日期格式模式
     * @return 解析后的LocalDateTime对象
     * @throws DateTimeParseException 如果解析失败
     */
    public static LocalDateTime parse(String dateStr, String pattern) {
        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 解析日期时间（默认格式：yyyy-MM-dd HH:mm:ss）
     *
     * @param dateStr 日期时间字符串
     * @return 解析后的LocalDateTime对象
     * @throws DateTimeParseException 如果解析失败
     */
    public static LocalDateTime parse(String dateStr) {
        return parse(dateStr, NORM_DATETIME_PATTERN);
    }

    // ==================== 日期加减操作 ====================

    /**
     * 加年
     *
     * @param dateTime 原始日期时间
     * @param years    要加的年数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addYears(LocalDateTime dateTime, int years) {
        return dateTime.plusYears(years);
    }

    /**
     * 加年
     *
     * @param date 原始日期时间
     * @param years    要加的年数（可为负数）
     * @return 计算后的日期时间
     */
    public static Date addYears(Date date, int years) {
        return toDate(addYears(toLocalDateTime(date), years));
    }

    /**
     * 减年
     *
     * @param date 原始日期时间
     * @param years    要减的年数
     * @return 计算后的日期时间
     */
    public static Date minusYears(Date date, int years) {
        return toDate(minusYears(toLocalDateTime(date), years));
    }

    /**
     * 减年
     *
     * @param dateTime 原始日期时间
     * @param years    要减的年数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusYears(LocalDateTime dateTime, int years) {
        return dateTime.minusYears(years);
    }

    /**
     * 加月
     *
     * @param dateTime 原始日期时间
     * @param months   要加的月数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addMonths(LocalDateTime dateTime, int months) {
        return dateTime.plusMonths(months);
    }

    /**
     * 加月
     *
     * @param date 原始日期时间
     * @param months   要加的月数（可为负数）
     * @return 计算后的日期时间
     */
    public static Date addMonths(Date date, int months) {
        return toDate(addMonths(toLocalDateTime(date), months));
    }

    /**
     * 减月
     *
     * @param dateTime 原始日期时间
     * @param months   要减的月数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusMonths(LocalDateTime dateTime, int months) {
        return dateTime.minusMonths(months);
    }

    /**
     * 加日
     *
     * @param dateTime 原始日期时间
     * @param days     要加的天数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addDays(LocalDateTime dateTime, int days) {
        return dateTime.plusDays(days);
    }

    /**
     * 减日
     *
     * @param dateTime 原始日期时间
     * @param days     要减的天数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusDays(LocalDateTime dateTime, int days) {
        return dateTime.minusDays(days);
    }

    /**
     * 加小时
     *
     * @param dateTime 原始日期时间
     * @param hours    要加的小时数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addHours(LocalDateTime dateTime, int hours) {
        return dateTime.plusHours(hours);
    }

    /**
     * 减小时
     *
     * @param dateTime 原始日期时间
     * @param hours    要减的小时数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusHours(LocalDateTime dateTime, int hours) {
        return dateTime.minusHours(hours);
    }

    /**
     * 加分钟
     *
     * @param dateTime 原始日期时间
     * @param minutes  要加的分钟数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addMinutes(LocalDateTime dateTime, int minutes) {
        return dateTime.plusMinutes(minutes);
    }

    /**
     * 减分钟
     *
     * @param dateTime 原始日期时间
     * @param minutes  要减的分钟数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusMinutes(LocalDateTime dateTime, int minutes) {
        return dateTime.minusMinutes(minutes);
    }

    /**
     * 加秒
     *
     * @param dateTime 原始日期时间
     * @param seconds  要加的秒数（可为负数）
     * @return 计算后的日期时间
     */
    public static LocalDateTime addSeconds(LocalDateTime dateTime, int seconds) {
        return dateTime.plusSeconds(seconds);
    }

    /**
     * 减秒
     *
     * @param dateTime 原始日期时间
     * @param seconds  要减的秒数
     * @return 计算后的日期时间
     */
    public static LocalDateTime minusSeconds(LocalDateTime dateTime, int seconds) {
        return dateTime.minusSeconds(seconds);
    }

    // ==================== 日期部分获取 ====================

    /**
     * 获取年份
     *
     * @param date 日期时间对象
     * @return 年份
     */
    public static int getYear(Date date) {
        return getYear(toLocalDateTime(date));
    }

    /**
     * 获取年份
     *
     * @param dateTime 日期时间对象
     * @return 年份
     */
    public static int getYear(LocalDateTime dateTime) {
        return dateTime.getYear();
    }

    /**
     * 获取月份枚举
     *
     * @param dateTime 日期时间对象
     * @return 月份枚举值
     */
    public static Month getMonth(LocalDateTime dateTime) {
        return dateTime.getMonth();
    }


    /**
     * 获取月份数字（1-12）
     *
     * @param date 日期时间对象
     * @return 月份数字
     */
    public static int getMonthValue(Date date) {
        return getMonthValue(toLocalDateTime(date));
    }

    /**
     * 获取月份数字（1-12）
     *
     * @param dateTime 日期时间对象
     * @return 月份数字
     */
    public static int getMonthValue(LocalDateTime dateTime) {
        return dateTime.getMonthValue();
    }

    /**
     * 获取当月天数
     *
     * @param dateTime 日期时间对象
     * @return 当月天数（1-31）
     */
    public static int getDayOfMonth(LocalDateTime dateTime) {
        return dateTime.getDayOfMonth();
    }

    /**
     * 获取星期枚举
     *
     * @param dateTime 日期时间对象
     * @return 星期枚举值
     */
    public static DayOfWeek getDayOfWeek(LocalDateTime dateTime) {
        return dateTime.getDayOfWeek();
    }

    /**
     * 获取小时（24小时制）
     *
     * @param dateTime 日期时间对象
     * @return 小时（0-23）
     */
    public static int getHour(LocalDateTime dateTime) {
        return dateTime.getHour();
    }

    /**
     * 获取分钟
     *
     * @param dateTime 日期时间对象
     * @return 分钟（0-59）
     */
    public static int getMinute(LocalDateTime dateTime) {
        return dateTime.getMinute();
    }

    /**
     * 获取秒
     *
     * @param dateTime 日期时间对象
     * @return 秒（0-59）
     */
    public static int getSecond(LocalDateTime dateTime) {
        return dateTime.getSecond();
    }

    // ==================== 日期计算 ====================

    /**
     * 计算两个日期时间之间的差值
     *
     * @param start 开始时间
     * @param end   结束时间
     * @param unit  时间单位（ChronoUnit）
     * @return 时间差值
     */
    public static long between(LocalDateTime start, LocalDateTime end, ChronoUnit unit) {
        return unit.between(start, end);
    }

    /**
     * 判断日期是否在指定范围内
     *
     * @param date  待判断日期
     * @param start 范围开始时间（包含）
     * @param end   范围结束时间（包含）
     * @return 是否在范围内
     */
    public static boolean isInRange(LocalDateTime date, LocalDateTime start, LocalDateTime end) {
        return !date.isBefore(start) && !date.isAfter(end);
    }

    // ==================== 时间范围处理 ====================

    /**
     * 获取日期的开始时间（00:00:00）
     *
     * @param date 日期时间
     * @return 当天的开始时间
     */
    public static Date beginOfDay(Date date) {
        return toDate(beginOfDay(toLocalDateTime(date)));
    }

    /**
     * 获取日期的开始时间（00:00:00）
     *
     * @param dateTime 日期时间
     * @return 当天的开始时间
     */
    public static LocalDateTime beginOfDay(LocalDateTime dateTime) {
        return dateTime.toLocalDate().atStartOfDay();
    }

    /**
     * 获取月份的开始时间
     *
     * @param date 日期时间
     * @return 当月第一天 00:00:00
     */
    public static Date beginOfMonth(Date date) {
        return toDate(beginOfMonth(toLocalDateTime(date)));
    }

    /**
     * 获取月份的开始时间
     *
     * @param dateTime 日期时间
     * @return 当月第一天 00:00:00
     */
    public static LocalDateTime beginOfMonth(LocalDateTime dateTime) {
        return dateTime.withDayOfMonth(1).toLocalDate().atStartOfDay();
    }

    /**
     * 获取季度的开始时间
     *
     * @param dateTime 日期时间
     * @return 当季度第一天 00:00:00
     */
    public static LocalDateTime beginOfQuarter(LocalDateTime dateTime) {
        int month = dateTime.getMonthValue();
        int quarterStartMonth = ((month - 1) / 3) * 3 + 1;
        return dateTime.withMonth(quarterStartMonth).withDayOfMonth(1).toLocalDate().atStartOfDay();
    }

    /**
     * 获取年份的开始时间
     *
     * @param date 日期时间
     * @return 当年第一天 00:00:00
     */
    public static Date beginOfYear(Date date) {
        return toDate(beginOfYear(toLocalDateTime(date)));
    }
    /**
     * 获取年份的开始时间
     *
     * @param dateTime 日期时间
     * @return 当年第一天 00:00:00
     */
    public static LocalDateTime beginOfYear(LocalDateTime dateTime) {
        return dateTime.withDayOfYear(1).toLocalDate().atStartOfDay();
    }



    /**
     * 获取日期当天的结束时间（23:59:59.999999999）
     *
     * @param date 日期时间
     * @return 当天的结束时间
     */
    public static Date endOfDay(Date date) {
        return toDate(endOfDay(toLocalDateTime(date)));
    }

    /**
     * 获取日期当天的结束时间（23:59:59.999999999）
     *
     * @param dateTime 日期时间
     * @return 当天的结束时间
     */
    public static LocalDateTime endOfDay(LocalDateTime dateTime) {
        return dateTime.toLocalDate().atTime(LocalTime.MAX);
    }

    /**
     * 获取月份结束时间
     *
     * @param dateTime 日期时间
     * @return 当月最后一天 23:59:59.999999999
     */
    public static LocalDateTime endOfMonth(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.lastDayOfMonth()).toLocalDate().atTime(LocalTime.MAX);
    }


    /**
     * 获取月份结束时间
     *
     * @param date 日期时间
     * @return 当月最后一天 23:59:59.999999999
     */
    public static Date endOfMonth(Date dateTime) {
        return toDate(endOfMonth(toLocalDateTime(dateTime)));
    }

    /**
     * 获取季度结束时间
     *
     * @param dateTime 日期时间
     * @return 当季度最后一天 23:59:59.999999999
     */
    public static LocalDateTime endOfQuarter(LocalDateTime dateTime) {
        int month = dateTime.getMonthValue();
        int quarterEndMonth = ((month - 1) / 3 + 1) * 3;
        return dateTime.withMonth(quarterEndMonth)
                .with(TemporalAdjusters.lastDayOfMonth())
                .toLocalDate().atTime(LocalTime.MAX);
    }

    /**
     * 获取年份结束时间
     *
     * @param date 日期时间
     * @return 当年最后一天 23:59:59.999999999
     */
    public static Date endOfYear(Date date) {
        return toDate(endOfYear(toLocalDateTime(date)));
    }
    /**
     * 获取年份结束时间
     *
     * @param dateTime 日期时间
     * @return 当年最后一天 23:59:59.999999999
     */
    public static LocalDateTime endOfYear(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.lastDayOfYear()).toLocalDate().atTime(LocalTime.MAX);
    }

    /**
     * 获取周的开始时间（周一）
     *
     * @param dateTime 日期时间
     * @return 当周周一 00:00:00
     */
    public static LocalDateTime beginOfWeek(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                .toLocalDate().atStartOfDay();
    }

    /**
     * 获取周的结束时间（周日）
     *
     * @param dateTime 日期时间
     * @return 当周周日 23:59:59.999999999
     */
    public static LocalDateTime endOfWeek(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
                .toLocalDate().atTime(LocalTime.MAX);
    }

    // ==================== 日期范围生成 ====================

    /**
     * 生成日期范围列表
     *
     * @param start 开始时间（包含）
     * @param end   结束时间（包含）
     * @param unit  时间单位
     * @return 日期时间列表
     */
    public static List<LocalDateTime> range(LocalDateTime start, LocalDateTime end, ChronoUnit unit) {
        if (start.isAfter(end)) {
            throw new IllegalArgumentException("Start must be before end");
        }

        List<LocalDateTime> result = new ArrayList<>();
        long distance = unit.between(start, end);

        for (long i = 0; i <= distance; i++) {
            result.add(start.plus(i, unit));
        }
        return result;
    }

    /**
     * 日期范围转换处理
     *
     * @param <T>   返回类型
     * @param start 开始时间
     * @param end   结束时间
     * @param unit  时间单位
     * @param func  转换函数
     * @return 转换后的结果列表
     */
    public static <T> List<T> rangeFunc(LocalDateTime start, LocalDateTime end, ChronoUnit unit, Function<LocalDateTime, T> func) {
        return range(start, end, unit).stream().map(func).collect(Collectors.toList());
    }

    /**
     * 日期范围消费处理
     *
     * @param start    开始时间
     * @param end      结束时间
     * @param unit     时间单位
     * @param consumer 消费函数
     */
    public static void rangeConsume(LocalDateTime start, LocalDateTime end, ChronoUnit unit, Consumer<LocalDateTime> consumer) {
        range(start, end, unit).forEach(consumer);
    }

    // ==================== 特殊日期判断 ====================

    /**
     * 判断是否为闰年
     *
     * @param dateTime 日期时间
     * @return 是否为闰年
     */
    public static boolean isLeapYear(LocalDateTime dateTime) {
        return dateTime.toLocalDate().isLeapYear();
    }

    /**
     * 判断是否为周末（周六或周日）
     *
     * @param dateTime 日期时间
     * @return 是否为周末
     */
    public static boolean isWeekend(LocalDateTime dateTime) {
        DayOfWeek day = dateTime.getDayOfWeek();
        return day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY;
    }

    /**
     * 判断是否为工作日（周一到周五）
     *
     * @param dateTime 日期时间
     * @return 是否为工作日
     */
    public static boolean isWorkday(LocalDateTime dateTime) {
        return !isWeekend(dateTime);
    }

    // ==================== 时间戳操作 ====================

    /**
     * 时间戳转LocalDateTime
     *
     * @param timestamp 毫秒时间戳
     * @return 对应的日期时间
     */
    public static LocalDateTime fromTimestamp(long timestamp) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
    }

    /**
     * 日期时间转时间戳（毫秒）
     *
     * @param dateTime 日期时间
     * @return 毫秒时间戳
     */
    public static long toTimestamp(LocalDateTime dateTime) {
        return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    // ==================== 时区转换 ====================

    /**
     * 时区转换
     *
     * @param dateTime  原始日期时间
     * @param targetZone 目标时区
     * @return 目标时区的日期时间
     */
    public static LocalDateTime convertTimeZone(LocalDateTime dateTime, ZoneId targetZone) {
        ZonedDateTime zoned = dateTime.atZone(ZoneId.systemDefault());
        return zoned.withZoneSameInstant(targetZone).toLocalDateTime();
    }

    // ==================== 日期比较 ====================

    /**
     * 比较两个日期时间
     *
     * @param dt1 日期时间1
     * @param dt2 日期时间2
     * @return 比较结果（负数：dt1 < dt2，0：相等，正数：dt1 > dt2）
     */
    public static int compare(Date dt1, Date dt2) {
        return toLocalDateTime(dt1).compareTo(toLocalDateTime(dt2));
    }
    /**
     * 比较两个日期时间
     *
     * @param dt1 日期时间1
     * @param dt2 日期时间2
     * @return 比较结果（负数：dt1 < dt2，0：相等，正数：dt1 > dt2）
     */
    public static int compare(LocalDateTime dt1, LocalDateTime dt2) {
        return dt1.compareTo(dt2);
    }

    /**
     * 判断是否为同一天
     *
     * @param dt1 日期时间1
     * @param dt2 日期时间2
     * @return 是否同一天
     */
    public static boolean isSameDay(LocalDateTime dt1, LocalDateTime dt2) {
        return dt1.toLocalDate().equals(dt2.toLocalDate());
    }

    // ==================== 实用功能 ====================

    /**
     * 计算年龄
     *
     * @param birthDate 出生日期
     * @return 年龄（周岁）
     */
    public static int calculateAge(LocalDate birthDate) {
        return Period.between(birthDate, LocalDate.now()).getYears();
    }

    /**
     * 判断两个时间段是否重叠
     *
     * @param start1 时间段1开始
     * @param end1   时间段1结束
     * @param start2 时间段2开始
     * @param end2   时间段2结束
     * @return 是否重叠
     */
    public static boolean isOverlap(
            LocalDateTime start1, LocalDateTime end1,
            LocalDateTime start2, LocalDateTime end2
    ) {
        return !start1.isAfter(end2) && !start2.isAfter(end1);
    }

    /**
     * 获取季度（1-4）
     *
     * @param dateTime 日期时间
     * @return 所属季度
     */
    public static int getQuarter(LocalDateTime dateTime) {
        return (dateTime.getMonthValue() - 1) / 3 + 1;
    }


    /**
     * 获取月末最后一天
     *
     * @param dateTime 日期时间
     * @return 当月最后一天
     */
    public static int lastDayOfMonth(LocalDateTime dateTime) {
        return dateTime.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
    }

    /**
     * 获取下一个工作日（跳过周末）
     *
     * @param dateTime 起始日期
     * @return 下一个工作日
     */
    public static LocalDateTime nextWorkday(LocalDateTime dateTime) {
        LocalDateTime result = dateTime.plusDays(1);
        while (isWeekend(result)) {
            result = result.plusDays(1);
        }
        return result;
    }

    /**
     * 获取远期截止日期（2999年12月31日）
     * 常用于表示无截止日期限制的场景
     *
     * @return 2199年12月31日 23:59:59.999999999
     */
    public static LocalDateTime getMaxDate() {
        return LocalDateTime.of(2999, 12, 31, 23, 59, 59, 999999999);
    }

    /**
     * 获取远期截止日期（2999年12月31日）
     * 常用于表示无截止日期限制的场景
     *
     * @return 2199年12月31日
     */
    public static Date getMaxDateAsDate() {
        return toDate(getMaxDate());
    }
}