
package cokr.xit.ens.core.utils;

import java.sql.*;
import java.text.*;
import java.time.*;
import java.time.format.*;
import java.util.Date;
import java.util.*;

import org.apache.commons.lang.*;

import lombok.extern.slf4j.*;

@Slf4j
public class DateUtil {

    private static final String DEFAULT_YMD_DT_FMT ="yyyy-MM-dd HH:mm:ss";

    /**
     * <pre>메소드 설명: 절대시간(단위: milliesecond)을 현재 시간으로 반환 한다.</pre>
     *
     * @param millisec
     * @param fmt
     * @return String 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static String absTimeMillisecToDate(Long millisec, String fmt) {
        if (millisec == null)
            return null;
        return absTimeToDate(millisec, fmt);
    }

    /**
     * <pre>메소드 설명: 절대시간(단위: second)을 현재 시간으로 반환 한다.</pre>
     *
     * @param sec
     * @param fmt
     * @return String 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static String absTimeSecToDate(int sec, String fmt) {
        return absTimeSecToDate(sec * 1L, fmt);
    }

    /**
     * <pre>메소드 설명: 절대시간(단위: second)을 현재 시간으로 반환 한다.</pre>
     *
     * @param sec
     * @param fmt
     * @return String 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static String absTimeSecToDate(Long sec, String fmt) {
        if (sec == null)
            return null;
        return absTimeToDate(sec * 1000L, fmt);
    }

    /**
     * <pre>메소드 설명: 절대시간(단위: ms)을 현재 시간으로 반환 한다.</pre>
     *
     * @param millionSec
     * @param fmt
     * @return String 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static String absTimeToDate(Long millionSec, String fmt) {
        if (fmt == null || "".equals(fmt)) fmt = "yyyy-MM-dd HH:mm:ss";

        Date date = new Date(millionSec);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(fmt);

        return simpleDateFormat.format(date);
    }


    /**
     * <pre>메소드 설명: 시간을 절대시간(단위: sec)로 반환 한다.</pre>
     *
     * @param expireDt 만료일시(yyyyMMddHHmmss)
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static Long dateToAbsTimeSec(String expireDt) {
        return dateToAbsTime(expireDt) / 1000;
    }

    /**
     * <pre>메소드 설명: 시간을 절대시간(단위: sec)로 반환 한다.</pre>
     *
     * @param year
     * @param month
     * @param day
     * @param hour
     * @param minute
     * @param sec
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static Long dateToAbsTimeSec(int year, int month, int day, int hour, int minute, int sec) {
        return dateToAbsTime(year, month, day, hour, minute, sec) / 1000;
    }

    /**
     * <pre>메소드 설명: 시간을 절대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param expireDt 만료일시(yyyyMMddHHmmss)
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    @SuppressWarnings("finally")
    public static Long dateToAbsTime(String expireDt) {
        if (expireDt == null)
            return 0L;

        expireDt = expireDt.replaceAll("[^0-9]", "");
        if ("".equals(expireDt))
            return 0L;

        int year, month, day, hour, minute, sec;
        year = month = day = hour = minute = sec = 0;
        try {
            year = Integer.parseInt(expireDt.substring(0, 4));
            month = Integer.parseInt(expireDt.substring(4, 6));
            day = Integer.parseInt(expireDt.substring(6, 8));
            hour = Integer.parseInt(expireDt.substring(8, 10));
            minute = Integer.parseInt(expireDt.substring(10, 12));
            sec = Integer.parseInt(expireDt.substring(12, 14));
        } catch (Exception e) {
            log.error(String.format("dateToAbsTime fail... expireDt is [%s]", expireDt));
        } finally {
            return dateToAbsTime(year, month, day, hour, minute, sec);
        }

    }

    /**
     * <pre>메소드 설명: 시간을 절대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param year
     * @param month
     * @param day
     * @param hour
     * @param minute
     * @param sec
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    @SuppressWarnings("deprecation")
    public static Long dateToAbsTime(int year, int month, int day, int hour, int minute, int sec) {
        if (month < 0) month = 1;
        if (month > 11) month = 12;
        if (day < 1 || day > 31) day = 1;
        if (hour < 0 || hour > 23) hour = 0;
        if (minute < 0 || minute > 59) minute = 0;
        if (sec < 0 || sec > 59) sec = 0;

        Date curDate = new Date(year - 1900, month - 1, day, hour, minute, sec);
        return curDate.getTime();
    }


    /**
     * <pre>메소드 설명: 시간을 상대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param expireDt 만료일시(yyyyMMddHHmmss)
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    @SuppressWarnings("finally")
    public static Long dateToRelTime(String expireDt) {
        if (expireDt == null)
            return 0L;

        expireDt = expireDt.replaceAll("[^0-9]", "");
        if ("".equals(expireDt))
            return 0L;

        int year, month, day, hour, minute, sec;
        year = month = day = hour = minute = sec = 0;
        try {
            year = Integer.parseInt(expireDt.substring(0, 4));
            month = Integer.parseInt(expireDt.substring(4, 6));
            day = Integer.parseInt(expireDt.substring(6, 8));
            hour = Integer.parseInt(expireDt.substring(8, 10));
            minute = Integer.parseInt(expireDt.substring(10, 12));
            sec = Integer.parseInt(expireDt.substring(12, 14));
        } catch (Exception e) {
            log.error(String.format("dateToRelTime fail... expireDt is [%s]", expireDt));
        } finally {
            return dateToRelTime(year, month, day, hour, minute, sec);
        }

    }

    /**
     * <pre>메소드 설명: 시간을 상대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param year
     * @param month
     * @param day
     * @param hour
     * @param minute
     * @param sec
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    @SuppressWarnings("deprecation")
    public static Long dateToRelTime(int year, int month, int day, int hour, int minute, int sec) {
        if (month < 0) month = 1;
        if (month > 11) month = 12;
        if (day < 1 || day > 31) day = 1;
        if (hour < 0 || hour > 23) hour = 0;
        if (minute < 0 || minute > 59) minute = 0;
        if (sec < 0 || sec > 59) sec = 0;

        return dateToRelTime(LocalDateTime.of(year, month, day, hour, minute, sec));
    }

    /**
     * <pre>메소드 설명: 시간을 상대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param date
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static Long dateToRelTime(LocalDateTime date) {

        Long fromDateMs = DateUtil.dateToAbsTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        Long toDateMs = DateUtil.dateToAbsTime(date.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));

        return toDateMs - fromDateMs;
    }

    /**
     * <pre>메소드 설명: 일수를 상대시간(단위: ms)로 반환 한다.</pre>
     *
     * @param days
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2021. 11. 19.
     */
    public static Long daysToRelTime(int days) {
        if (days < 0) days = 0;
        return days * 24L * 60L * 60L * 1000L;
    }

    /**
     * <pre>메소드 설명: 일시(yyyyMMddHHmmss)를 LocalDateTime 으로 반환 한다.</pre>
     *
     * @param yyyyMMddHHmmss
     * @return Long 요청처리 후 응답객체
     * @author: 박민규
     * @date: 2022. 3. 15.
     */
    public static LocalDateTime toLocalDateTime(String yyyyMMddHHmmss) {
        String date = Optional.ofNullable(yyyyMMddHHmmss).orElseGet(() -> null);

        if (date == null) return null;

        date = date.replaceAll("[^0-9]", "");
        if (date.length() != 14)
            return null;


        return LocalDateTime.of(
                Integer.parseInt(date.substring(0, 4))
                , Integer.parseInt(date.substring(4, 6))
                , Integer.parseInt(date.substring(6, 8))
                , Integer.parseInt(date.substring(8, 10))
                , Integer.parseInt(date.substring(10, 12))
                , Integer.parseInt(date.substring(12, 14))
        );
    }


    /**
     * 날짜(date) 사이의 간격을 계산하여 일자(day)로 반환 한다.
     *
     * @param fromDate
     * @param toDate
     * @return
     */
    public static int daysByFromBetweenTo(Date fromDate, Date toDate) {
        return daysByFromBetweenTo(fromDate.getTime(), toDate.getTime());
    }

    /**
     * 날짜(date) 사이의 간격을 계산하여 일자(day)로 반환 한다.
     *
     * @param fromDate
     * @param toDate
     * @return
     */
    public static int daysByFromBetweenTo(LocalDateTime fromDate, LocalDateTime toDate) {
        return daysByFromBetweenTo(Timestamp.valueOf(fromDate).getTime(), Timestamp.valueOf(toDate).getTime());
    }

    /**
     * 초(sec) 사이의 간격을 계산하여 일자(day)로 반환 한다.
     *
     * @param fromSec
     * @param toSec
     * @return
     */
    public static int daysByFromBetweenTo(int fromSec, int toSec) {
        return daysByFromBetweenTo(fromSec * 1000L, toSec * 1000L);
    }

    /**
     * 밀리초(ms) 사이의 간격을 계산하여 일자(day)로 반환 한다.
     *
     * @param fromMs
     * @param toMs
     * @return
     */
    public static int daysByFromBetweenTo(long fromMs, long toMs) {
        return (int) ((toMs - fromMs) / (24 * 60 * 60 * 1000));
    }

    /**
     * 밀리초(ms) 사이의 간격을 계산하여 초(second)로 반환 한다.
     *
     * @param fromDate
     * @param toDate
     * @toDate
     */
    public static int secByFromBetweenTo(LocalDateTime fromDate, LocalDateTime toDate) {
        return secByFromBetweenTo(Timestamp.valueOf(fromDate).getTime(), Timestamp.valueOf(toDate).getTime());
    }

    /**
     * 밀리초(ms) 사이의 간격을 계산하여 초(second)로 반환 한다.
     *
     * @param fromMs
     * @param toMs
     * @return
     */
    public static int secByFromBetweenTo(long fromMs, long toMs) {
        return (int) ((toMs - fromMs) / (1000));
    }


    /**
     * 초(sec)를 일수(day)로 반환 한다.
     *
     * @return
     */
    public static int secToDays(int sec) {
        return msToDays(sec * 1000L);
    }

    /**
     * 밀리초(ms)를 일수(day)로 반환 한다.
     *
     * @return
     */
    public static int msToDays(long ms) {
        return (int) (ms / (24 * 60 * 60 * 1000));
    }


    /**
     * 현재날짜에 초(sec)를 더한 날짜를 반환 한다.
     *
     * @param sec
     * @param pattern
     * @return
     */
    public static String relTimeSecToDate(int sec, String pattern) {
        if ("".equals(pattern) || pattern == null)
            pattern = "yyyyMMdd";

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis((sec * 1000L) + (new Date().getTime()));
        Date date = calendar.getTime();

        return new SimpleDateFormat(pattern).format(date);
    }

    /**
     * 특정날짜에 초(sec)를 더한 날짜를 반환 한다.
     *
     * @param sec
     * @param pattern
     * @return
     */
    public static String addRelTimeSecToDate(LocalDateTime localDateTime, int sec, String pattern) {
        if ("".equals(pattern) || pattern == null)
            pattern = "yyyyMMdd";

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis((sec * 1000L) + Timestamp.valueOf(localDateTime).getTime());
        Date date = calendar.getTime();

        return new SimpleDateFormat(pattern).format(date);
    }

    /**
     * yyyy-MM-dd'T'HH:mm:ss String > fmt 포맷으로 변환(null 인경우 yyyy-MM-dd HH:mm:ss String 으로 반환)
     * @param timeT
     * @param fmt 변환할 fromat
     * @return
     */
    public static String getTimeOfTimeT(String timeT, String fmt) {
        return LocalDateTime.parse(timeT, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))
            .format(DateTimeFormatter.ofPattern(StringUtils.isEmpty(fmt)? DEFAULT_YMD_DT_FMT: fmt));
    }

    /**
     * yyyyMMddHHmmss String > yyyy-MM-dd'T'HH:mm:ss String 으로 반환
     * @param time yyyyMMddHHmmss String
     *
     * @return yyyy-MM-dd'T'HH:mm:ss String
     */
    public static String getTimeTOfTime(String time) {
        return LocalDateTime.parse(time, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
            .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
    }

    /**
     * 오늘 날짜 : 년-월-일 시분초
     *
     * @param userFormat 사용자가 원하는 포멧
     * @return String
     */
    public static String getTodayAndNowTime(final String userFormat) {
        String pattern = "";

        try {
            pattern = DEFAULT_YMD_DT_FMT;
            if (!org.apache.commons.lang3.StringUtils.isEmpty(userFormat)) {
                pattern = userFormat;
            }
        } catch (Exception e) {
            log.error("DateUtils::getTodayAndNowTime", e);
        }

        return localDateTime().format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * LocalDateTime 객체 반환
     *
     * @return LocalDateTime
     */
    public static LocalDateTime localDateTime() {
        return LocalDateTime.now();
    }

    /**
     * <pre>
     * 지난 날짜 확인 : 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함.
     * sourceDate > compareDate = true
     * sourceDate < compareDate = false
     * sourceDate == compareDate = false
     *
     * @param sourceDate  시작 년-월-일
     * @param compareDate 비교 년-월-일
     * @return boolean
     * </pre>
     */
    public static boolean isAfterLocalDateTimeT(final String sourceDate, final String compareDate) {
        boolean result = false;

        try {
            LocalDateTime source = parseLocalDateTime(sourceDate);
            result = source.isAfter(parseLocalDateTime(compareDate));
        } catch (Exception e) {
            log.error("DateUtils::isAfterLocalDate", e);
        }

        return result;
    }

    /**
     * <pre>
     * 날자 add : 2020-06-22T23:20:32
     * 빼기는 음수를 붙여서 넘기면 된다.
     * ex : -1 = 빼기 1
     *
     * @param targetDate 계산할 년-월-일
     * @param day        계산할 일
     * @return String
     * </pre>
     */
    public static String getAddDayT(final String targetDate, final int day) {
        return getCalculatorDateAndTime(targetDate, 0, 0, day, 0, 0, 0);
    }

    /**
     * LocalDate.parse 객체 반환
     *
     * @param target yyyy-MM-dd
     * @return LocalDate.parse(tartget);
     */
    public static LocalDate parseLocalDate(final String target) {
        return LocalDate.parse(target);
    }

    /**
     * LocalDateTime.parse 객체 반환
     *
     * @param target yyyy-MM-dd HH:mm:ss
     * @return LocalDateTime.parse(tartget);
     */
    public static LocalDateTime parseLocalDateTime(final String target) {
        return LocalDateTime.parse(target);
    }

    /**
     * <pre>
     * 현재시간을 microsecond 까지 포함한 형태로 반환
     * yyyyMMddHHmmssSSSSSSS
     * <pre>
     * @return String yyyyMMddHHmmssSSSSSSS 포맷으로
     */
    public static String getNowTimeMicrosecond() {
        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));
        // 6자리 나노초까지 포함된 포맷 지정
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSSS");
        return now.format(formatter);
    }

    /**
     * <pre>
     * 년 월 일 계산
     * 날짜만 계산 년-월-일 형태로 넘겨줘야함(안그럼 예외)
     * 해당월에 존재하지 않는 일을 넘겨도 예외
     * 빼기는 음수를 붙여서 넘기면 된다.
     * ex : -1 = 빼기 1
     *
     * @param targetDate 계산할 년-월-일
     * @param year       계산할 년
     * @param month      계산할 월
     * @param day        계산할 일
     * @return String
     * </pre>
     */
    public static String getCalculatorDate(final String targetDate, final int year, final int month, final int day) {
        String result = "";

        try {
            LocalDate localDate = parseLocalDate(targetDate);
            localDate = localDate.plusYears(year);
            localDate = localDate.plusMonths(month);
            localDate = localDate.plusDays(day);
            result = localDate.toString();
        } catch (Exception e) {
            log.error("DateUtils::getCalculatorDate", e);
        }

        return result;
    }

    /**
     * <pre>
     * 날짜&시간 계산 년-월-일THH:mm:ss 형태(안그럼 예외)
     * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함.
     * 빼기는 음수를 붙여서 ex : -1 = 빼기 1
     *
     * @param targetDate 계산할 년-월-일T시:분:초
     * @param year       계산할 년
     * @param month      계산할 월
     * @param day        계산할 일
     * @param hour       계산할 시간
     * @param minute     계산할 분
     * @param second     계산할 초
     * @return String
     * </pre>
     */
    public static String getCalculatorDateAndTime(final String targetDate, final int year, final int month, final int day, int hour, int minute, int second) {
        String result = "";

        try {
            LocalDateTime localDateTime = parseLocalDateTime(targetDate);
            localDateTime = localDateTime.plusYears(year);
            localDateTime = localDateTime.plusMonths(month);
            localDateTime = localDateTime.plusDays(day);
            localDateTime = localDateTime.plusHours(hour);
            localDateTime = localDateTime.plusMinutes(minute);
            localDateTime = localDateTime.plusSeconds(second);
            result = localDateTime.toString();
        } catch (Exception e) {
            log.error("DateUtils::getCalculatorDateAndTime", e);
        }

        return result;
    }

    public static void main(String[] args) {
        System.out.println(getAddDayT("2024-10-30T23:59:59", 1));
        System.out.println(isAfterLocalDateTimeT("2024-10-30T23:59:59", "2024-10-30T23:59:58"));
        System.out.println(isAfterLocalDateTimeT("2024-10-31T23:59:59", "2024-10-30T23:59:58"));
        System.out.println(getNowTimeMicrosecond());
        System.out.println(getAddDayT(getTimeTOfTime("20241030235959"), 1));
    }
}
