package egovframework.util;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Date;
import java.util.Locale;

/**
 * 날짜 관련 공통 유틸리티 클래스
 */
@Component
public class DateUtil {

    /**
     * 현재 날짜의 년월 정보를 "yyyyMM" 형식으로 반환
     * @return 년월 문자열 (예: "202405")
     */
    public String getCurrentYearMonth() {
        LocalDate now = LocalDate.now();
        return now.format(DateTimeFormatter.ofPattern("yyyyMM"));
    }

    /**
     * 현재 날짜를 지정된 형식으로 반환
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd", "yyyy/MM/dd")
     * @return 형식화된 날짜 문자열
     */
    public String getCurrentDate(String pattern) {
        LocalDate now = LocalDate.now();
        return now.format(DateTimeFormatter.ofPattern(pattern));
    }


    /**
     * 현재 날짜에 지정된 일수를 더하거나 뺀 날짜를 지정된 형식으로 반환
     *
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd")
     * @param days    더하거나 뺄 일수 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static String getCurrentDateAddDays(String pattern, int days) {
        LocalDate now = LocalDate.now();
        return now.plusDays(days).format(DateTimeFormatter.ofPattern(pattern));
    }
    
    /**
     * 현재 날짜에 지정된 개월수를 더하거나 뺀 날짜를 지정된 형식으로 반환
     *
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd")
     * @param months    더하거나 뺄 개월수 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static String getCurrentDateAddMonths(String pattern, int months) {
        LocalDate now = LocalDate.now();
        return now.plusMonths(months).format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 현재 날짜와 시간을 지정된 형식으로 반환
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
     * @return 형식화된 날짜 문자열
     */
    public static String getCurrentDateTime(String pattern) {
        LocalDateTime now = LocalDateTime.now();
        return now.format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 현재 시간을 30분 단위로 조정하여 지정된 형식으로 반환
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
     * @return 30분 단위로 조정된 날짜 문자열
     */
    public static String getCurrentDateTimeRoundedToHalfHour(String pattern) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime adjusted;

        int minute = now.getMinute();

        if (minute < 30) {
            // 0~29분이면 같은 시간의 30분으로 조정
            adjusted = now.withMinute(30).withSecond(0).withNano(0);
        } else {
            // 30~59분이면 다음 시간의 00분으로 조정
            adjusted = now.plusHours(1).withMinute(0).withSecond(0).withNano(0);
        }

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

    /**
     * 현재 시간을 30분 단위로 조정하고 1시간을 추가하여 지정된 형식으로 반환
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
     * @return 30분 단위로 조정되고 1시간 추가된 날짜 문자열
     */
    public static String getCurrentDateTimeRoundedToHalfHourPlusOneHour(String pattern) {
        // 먼저 30분 단위로 조정된 시간을 구한 후
        LocalDateTime adjusted = LocalDateTime.parse(
                getCurrentDateTimeRoundedToHalfHour("yyyy-MM-dd'T'HH:mm:ss"),
                DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
        );

        // 1시간 추가
        adjusted = adjusted.plusHours(1);

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


    /**
     * 주어진 시간을 30분 단위로 조정하여 반환
     * @param dateTime 조정할 LocalDateTime 객체
     * @return 30분 단위로 조정된 LocalDateTime 객체
     */
    public static LocalDateTime roundToHalfHour(LocalDateTime dateTime) {
        if (dateTime == null) {
            return null;
        }

        int minute = dateTime.getMinute();

        if (minute < 30) {
            // 0~29분이면 같은 시간의 30분으로 조정
            return dateTime.withMinute(30).withSecond(0).withNano(0);
        } else {
            // 30~59분이면 다음 시간의 00분으로 조정
            return dateTime.plusHours(1).withMinute(0).withSecond(0).withNano(0);
        }
    }



    /**
     * 문자열을 LocalDate 객체로 변환
     * @param dateStr 날짜 문자열
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd")
     * @return 변환된 LocalDate 객체, 변환 실패 시 null 반환
     */
    public static LocalDate parseLocalDate(String dateStr, String pattern) {
        if (dateStr == null || pattern == null) {
            return null;
        }
        try {
            return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 날짜 문자열을 LocalDate로 자동 변환
     * 8자리면 yyyyMMdd, 10자리면 yyyy-MM-dd 형식으로 자동 처리
     *
     * @param dateStr 날짜 문자열
     * @return LocalDate 객체, 변환 실패 시 null 반환
     */
    public static LocalDate parseDate(String dateStr) {
        if (dateStr == null) {
            return null;
        }

        try {
            String trimmed = dateStr.trim();
            if (trimmed.length() == 8) {
                // yyyyMMdd 형식
                return LocalDate.parse(trimmed, DateTimeFormatter.ofPattern("yyyyMMdd"));
            } else if (trimmed.length() == 10) {
                // yyyy-MM-dd 형식
                return LocalDate.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            } else {
                return null;
            }
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 문자열을 LocalDateTime 객체로 변환
     * @param dateTimeStr 날짜 시간 문자열
     * @param pattern 날짜 시간 형식 (예: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
     * @return 변환된 LocalDateTime 객체, 변환 실패 시 null 반환
     */
    public static LocalDateTime parseLocalDateTime(String dateTimeStr, String pattern) {
        if (dateTimeStr == null || pattern == null) {
            return null;
        }
        try {
            return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern));
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 날짜 문자열을 yyyy-MM-dd 형식으로 변환
     * 8자리면 yyyyMMdd에서 변환, 10자리면 이미 yyyy-MM-dd 형식으로 간주하고 그대로 반환
     *
     * @param dateStr 날짜 문자열 (yyyyMMdd 또는 yyyy-MM-dd 형식)
     * @return yyyy-MM-dd 형식의 날짜 문자열, 변환 실패 시 원본 문자열 반환
     */
    public static String formatDateString(String dateStr) {
        if (dateStr == null || dateStr.isEmpty()) {
            return dateStr;
        }

        try {
            String trimmed = dateStr.trim();
            if (trimmed.length() == 8) {
                // yyyyMMdd → yyyy-MM-dd
                LocalDate date = LocalDate.parse(trimmed, DateTimeFormatter.ofPattern("yyyyMMdd"));
                return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            } else if (trimmed.length() == 10) {
                // 이미 yyyy-MM-dd 형식
                return trimmed;
            } else {
                // 그 외 형식은 그대로 반환
                return dateStr;
            }
        } catch (Exception e) {
            return dateStr;
        }
    }

    /**
     * 날짜를 짧은 형식으로 변환 (YYYYMMDD 또는 YYYY-MM-DD -> YY.M.D)
     * 예: 20250903 -> 25.9.3
     * 예: 2025-09-03 -> 25.9.3
     *
     * @param dateStr 날짜 문자열 (yyyyMMdd 또는 yyyy-MM-dd 형식)
     * @return YY.M.D 형식의 날짜 문자열, 변환 실패 시 원본 문자열 반환
     */
    public static String formatToShortDate(String dateStr) {
        if (dateStr == null) {
            return dateStr;
        }

        try {
            String trimmed = dateStr.trim();
            String year, monthStr, dayStr;

            if (trimmed.length() == 8) {
                // YYYYMMDD 형식
                year = trimmed.substring(2, 4);  // YY
                monthStr = trimmed.substring(4, 6);  // MM
                dayStr = trimmed.substring(6, 8);    // DD
            } else if (trimmed.length() == 10) {
                // YYYY-MM-DD 형식
                year = trimmed.substring(2, 4);  // YY
                monthStr = trimmed.substring(5, 7);  // MM
                dayStr = trimmed.substring(8, 10);   // DD
            } else {
                // 그 외 형식은 원본 반환
                return dateStr;
            }

            int month = Integer.parseInt(monthStr);  // M (앞의 0 제거)
            int day = Integer.parseInt(dayStr);      // D (앞의 0 제거)

            return year + "." + month + "." + day;
        } catch (Exception e) {
            return dateStr;
        }
    }

    /**
     * LocalDate 객체를 문자열로 변환
     * @param date LocalDate 객체
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd")
     * @return 변환된 날짜 문자열, 변환 실패 시 null 반환
     */
    public static String formatLocalDate(LocalDate date, String pattern) {
        if (date == null || pattern == null) {
            return null;
        }
        return date.format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * LocalDateTime 객체를 문자열로 변환
     * @param dateTime LocalDateTime 객체
     * @param pattern 날짜 시간 형식 (예: "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss")
     * @return 변환된 날짜 시간 문자열, 변환 실패 시 null 반환
     */
    public static String formatLocalDateTime(Object dateTime, String pattern) {
        if (!(dateTime instanceof LocalDateTime) || pattern == null) {
            return "";
        }
        return ((LocalDateTime) dateTime).format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * Date 객체를 LocalDate 객체로 변환
     * @param date Date 객체
     * @return 변환된 LocalDate 객체, 변환 실패 시 null 반환
     */
    public static LocalDate toLocalDate(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * Date 객체를 LocalDateTime 객체로 변환
     * @param date Date 객체
     * @return 변환된 LocalDateTime 객체, 변환 실패 시 null 반환
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

    /**
     * LocalDate 객체를 Date 객체로 변환
     * @param localDate LocalDate 객체
     * @return 변환된 Date 객체, 변환 실패 시 null 반환
     */
    public static Date toDate(LocalDate localDate) {
        if (localDate == null) {
            return null;
        }
        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    /**
     * LocalDateTime 객체를 Date 객체로 변환
     * @param localDateTime LocalDateTime 객체
     * @return 변환된 Date 객체, 변환 실패 시 null 반환
     */
    public static Date toDate(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * 지정된 날짜에 일수를 더하거나 뺀 날짜 반환
     * @param date 기준 날짜
     * @param days 더하거나 뺄 일수 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static LocalDate addDays(LocalDate date, int days) {
        if (date == null) {
            return null;
        }
        return date.plusDays(days);
    }

    /**
     * 지정된 날짜에 월수를 더하거나 뺀 날짜 반환
     * @param date 기준 날짜
     * @param months 더하거나 뺄 월수 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static LocalDate addMonths(LocalDate date, int months) {
        if (date == null) {
            return null;
        }
        return date.plusMonths(months);
    }

    /**
     * 지정된 날짜에 연수를 더하거나 뺀 날짜 반환
     * @param date 기준 날짜
     * @param years 더하거나 뺄 연수 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static LocalDate addYears(LocalDate date, int years) {
        if (date == null) {
            return null;
        }
        return date.plusYears(years);
    }

    /**
     * 현재 날짜에 지정된 년/월/일을 더하거나 뺀 날짜를 지정된 형식으로 반환
     *
     * @param pattern 날짜 형식 (예: "yyyy-MM-dd", "yyyyMMdd")
     * @param type    년월일 구분 (Y: 년, M: 월, D: 일)
     * @param amount  더하거나 뺄 값 (양수: 더하기, 음수: 빼기)
     * @return 계산된 날짜
     */
    public static String getCurrentDateAdd(String pattern, String type, int amount) {
        LocalDate now = LocalDate.now();
        LocalDate result;

        if ("Y".equalsIgnoreCase(type) || "year".equalsIgnoreCase(type)) {
            result = now.plusYears(amount);
        } else if ("M".equalsIgnoreCase(type) || "month".equalsIgnoreCase(type)) {
            result = now.plusMonths(amount);
        } else if ("D".equalsIgnoreCase(type) || "day".equalsIgnoreCase(type)) {
            result = now.plusDays(amount);
        } else {
            result = now;
        }

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

    /**
     * 날짜가 범위 내에 있는지 확인
     * 날짜 문자열은 yyyyMMdd 또는 yyyy-MM-dd 형식 모두 지원
     *
     * @param targetDate 확인할 날짜 문자열
     * @param startDate  시작일 문자열
     * @param endDate    종료일 문자열
     * @return 범위 내 여부 (startDate <= targetDate <= endDate)
     */
    public static boolean isDateBetween(String targetDate, String startDate, String endDate) {
        if (targetDate == null || startDate == null || endDate == null) {
            return false;
        }

        try {
            LocalDate target = parseDate(targetDate);
            LocalDate start = parseDate(startDate);
            LocalDate end = parseDate(endDate);

            if (target == null || start == null || end == null) {
                return false;
            }

            return !target.isBefore(start) && !target.isAfter(end);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 두 날짜 사이의 일수 계산
     * @param startDate 시작 날짜
     * @param endDate 종료 날짜
     * @return 두 날짜 사이의 일수 (endDate - startDate)
     */
    public static long daysBetween(LocalDate startDate, LocalDate endDate) {
        if (startDate == null || endDate == null) {
            return 0;
        }
        return ChronoUnit.DAYS.between(startDate, endDate);
    }

    /**
     * 두 날짜 사이의 월수 계산
     * @param startDate 시작 날짜
     * @param endDate 종료 날짜
     * @return 두 날짜 사이의 월수 (endDate - startDate)
     */
    public static long monthsBetween(LocalDate startDate, LocalDate endDate) {
        if (startDate == null || endDate == null) {
            return 0;
        }
        return ChronoUnit.MONTHS.between(startDate, endDate);
    }

    /**
     * 두 날짜 사이의 연수 계산
     * @param startDate 시작 날짜
     * @param endDate 종료 날짜
     * @return 두 날짜 사이의 연수 (endDate - startDate)
     */
    public static long yearsBetween(LocalDate startDate, LocalDate endDate) {
        if (startDate == null || endDate == null) {
            return 0;
        }
        return ChronoUnit.YEARS.between(startDate, endDate);
    }

    /**
     * 두 날짜 사이의 기간 계산
     * @param startDate 시작 날짜
     * @param endDate 종료 날짜
     * @return 두 날짜 사이의 기간 (년, 월, 일)
     */
    public static Period periodBetween(LocalDate startDate, LocalDate endDate) {
        if (startDate == null || endDate == null) {
            return Period.ZERO;
        }
        return Period.between(startDate, endDate);
    }

    /**
     * 지정된 날짜의 요일 이름 반환 (한글)
     * @param date 날짜
     * @return 요일 이름 (예: "월요일", "화요일")
     */
    public static String getDayOfWeekName(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.KOREAN);
    }

    /**
     * 지정된 날짜의 요일 이름 반환 (영문)
     * @param date 날짜
     * @return 요일 이름 (예: "Monday", "Tuesday")
     */
    public static String getDayOfWeekNameInEnglish(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH);
    }

    /**
     * 지정된 날짜의 요일 이름 반환 (한글 약자)
     * @param date 날짜
     * @return 요일 이름 (예: "월", "화")
     */
    public static String getDayOfWeekShortName(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.KOREAN);
    }

    /**
     * 지정된 날짜가 주말인지 확인
     * @param date 날짜
     * @return 주말이면 true, 평일이면 false
     */
    public static boolean isWeekend(LocalDate date) {
        if (date == null) {
            return false;
        }
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
    }

    /**
     * 지정된 날짜가 평일인지 확인
     * @param date 날짜
     * @return 평일이면 true, 주말이면 false
     */
    public static boolean isWeekday(LocalDate date) {
        return !isWeekend(date);
    }

    /**
     * 지정된 날짜가 오늘인지 확인
     * @param date 날짜
     * @return 오늘이면 true, 아니면 false
     */
    public static boolean isToday(LocalDate date) {
        if (date == null) {
            return false;
        }
        return date.equals(LocalDate.now());
    }

    /**
     * 지정된 날짜의 월의 첫 날 반환
     * @param date 날짜
     * @return 월의 첫 날
     */
    public static LocalDate getFirstDayOfMonth(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * 지정된 날짜의 월의 마지막 날 반환
     * @param date 날짜
     * @return 월의 마지막 날
     */
    public static LocalDate getLastDayOfMonth(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 지정된 날짜의 연도의 첫 날 반환
     * @param date 날짜
     * @return 연도의 첫 날
     */
    public static LocalDate getFirstDayOfYear(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.with(TemporalAdjusters.firstDayOfYear());
    }

    /**
     * 지정된 날짜의 연도의 마지막 날 반환
     * @param date 날짜
     * @return 연도의 마지막 날
     */
    public static LocalDate getLastDayOfYear(LocalDate date) {
        if (date == null) {
            return null;
        }
        return date.with(TemporalAdjusters.lastDayOfYear());
    }

    /**
     * 지정된 날짜의 다음 특정 요일 반환
     * @param date 날짜
     * @param dayOfWeek 요일 (예: DayOfWeek.MONDAY)
     * @return 다음 특정 요일
     */
    public static LocalDate getNextDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) {
        if (date == null || dayOfWeek == null) {
            return null;
        }
        return date.with(TemporalAdjusters.next(dayOfWeek));
    }

    /**
     * 지정된 날짜의 이전 특정 요일 반환
     * @param date 날짜
     * @param dayOfWeek 요일 (예: DayOfWeek.MONDAY)
     * @return 이전 특정 요일
     */
    public static LocalDate getPreviousDayOfWeek(LocalDate date, DayOfWeek dayOfWeek) {
        if (date == null || dayOfWeek == null) {
            return null;
        }
        return date.with(TemporalAdjusters.previous(dayOfWeek));
    }

    /**
     * 지정된 날짜에 시간을 결합하여 LocalDateTime 객체 생성
     * @param date 날짜
     * @param hour 시
     * @param minute 분
     * @param second 초
     * @return 생성된 LocalDateTime 객체
     */
    public static LocalDateTime combineDateTime(LocalDate date, int hour, int minute, int second) {
        if (date == null) {
            return null;
        }
        return LocalDateTime.of(date, LocalTime.of(hour, minute, second));
    }

    /**
     * 세션 종료까지 남은 시간을 "HH:mm:ss" 형식으로 반환
     * @param request HttpServletRequest 객체
     * @param pattern 날짜 형식 (예: "HH:mm:ss")
     * @return 세션 종료까지 남은 시간 문자열 (HH:mm:ss 형식)
     */
    public static String getSessionExpiryTime(HttpServletRequest request, String pattern) {
        if (request == null) {
            return "00:00:00";
        }

        HttpSession session = request.getSession(false);
        if (session == null) {
            return "00:00:00";
        }

        // 세션 최대 유효 시간(초)
        int maxInactiveInterval = session.getMaxInactiveInterval();

        // 시, 분, 초 계산
        int hours = maxInactiveInterval / 3600;
        int minutes = (maxInactiveInterval % 3600) / 60;
        int seconds = maxInactiveInterval % 60;

        // HH:mm:ss 형식으로 반환
        return String.format("%02d:%02d:%02d", hours, minutes, seconds);
    }
}
