package incheon.com.cmm.util;

import de.huxhorn.sulky.ulid.ULID;

/**
 * ULID (Universally Unique Lexicographically Sortable Identifier) 유틸리티
 *
 * <p>파일ID, 첨부파일ID 등 시스템 전반에서 유니크한 ID 생성을 담당합니다.
 * ULID는 UUID와 달리 시간순 정렬이 가능하고 URL-safe한 특징이 있습니다.
 *
 * <p><b>ULID 특징:</b>
 * <ul>
 *   <li>26자리 문자열 (UUID는 36자리)</li>
 *   <li>시간순 정렬 가능 (앞 10자리가 timestamp)</li>
 *   <li>대소문자 구분 없음 (Crockford's base32)</li>
 *   <li>URL-safe (특수문자 없음)</li>
 *   <li>밀리초당 최대 2^80개 생성 가능</li>
 * </ul>
 *
 * <p><b>사용 예시:</b>
 * <pre>{@code
 * // 새로운 ULID 생성
 * String fileId = UlidUtil.generate();
 * // 예: "01ARZ3NDEKTSV4RRFFQ69G5FAV"
 *
 * // 접두어 포함 ULID 생성
 * String atchFileId = UlidUtil.generateWithPrefix("FILE");
 * // 예: "FILE_01ARZ3NDEKTSV4RRFFQ69G5FAV"
 *
 * // ULID 유효성 검증
 * boolean isValid = UlidUtil.isValid("01ARZ3NDEKTSV4RRFFQ69G5FAV");
 *
 * // ULID에서 시간 추출
 * long timestamp = UlidUtil.getTimestamp("01ARZ3NDEKTSV4RRFFQ69G5FAV");
 * }</pre>
 *
 * @author Incheon City Development Team
 * @since 2025-01-17
 */
public class UlidUtil {

    private static final ULID ulid = new ULID();

    /**
     * ULID 정규식 패턴 (26자리 Crockford's base32)
     */
    private static final String ULID_PATTERN = "^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$";

    /**
     * Private constructor to prevent instantiation
     */
    private UlidUtil() {
        throw new IllegalStateException("Utility class");
    }

    /**
     * 새로운 ULID 생성
     *
     * @return 26자리 ULID 문자열
     */
    public static String generate() {
        return ulid.nextULID();
    }

    /**
     * 접두어를 포함한 ULID 생성
     *
     * <p>특정 도메인이나 용도를 구분하기 위해 접두어를 추가합니다.
     *
     * @param prefix 접두어 (예: "FILE", "USER", "DOC")
     * @return 접두어_ULID 형식의 문자열
     */
    public static String generateWithPrefix(String prefix) {
        if (prefix == null || prefix.trim().isEmpty()) {
            return generate();
        }
        return prefix + "_" + generate();
    }

    /**
     * ULID 유효성 검증
     *
     * @param ulidString 검증할 ULID 문자열
     * @return 유효한 ULID인 경우 true
     */
    public static boolean isValid(String ulidString) {
        if (ulidString == null || ulidString.length() != 26) {
            return false;
        }

        // 접두어가 있는 경우 제거 후 검증
        if (ulidString.contains("_")) {
            String[] parts = ulidString.split("_");
            if (parts.length != 2) {
                return false;
            }
            ulidString = parts[1];
        }

        return ulidString.toUpperCase().matches(ULID_PATTERN);
    }

    /**
     * ULID에서 timestamp 추출 (밀리초)
     *
     * <p>ULID의 앞 10자리는 Unix timestamp(밀리초)를 나타냅니다.
     *
     * @param ulidString ULID 문자열
     * @return Unix timestamp (밀리초)
     * @throws IllegalArgumentException 유효하지 않은 ULID인 경우
     */
    public static long getTimestamp(String ulidString) {
        if (!isValid(ulidString)) {
            throw new IllegalArgumentException("Invalid ULID: " + ulidString);
        }

        // 접두어 제거
        if (ulidString.contains("_")) {
            ulidString = ulidString.substring(ulidString.lastIndexOf("_") + 1);
        }

        // 앞 10자리 추출하여 timestamp 계산
        String timestampPart = ulidString.substring(0, 10);
        return decodeTimestamp(timestampPart);
    }

    /**
     * Crockford's base32로 인코딩된 timestamp 디코드
     *
     * @param encoded 인코딩된 timestamp 문자열 (10자리)
     * @return Unix timestamp (밀리초)
     */
    private static long decodeTimestamp(String encoded) {
        String alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
        long timestamp = 0;

        for (char c : encoded.toUpperCase().toCharArray()) {
            timestamp = timestamp * 32 + alphabet.indexOf(c);
        }

        return timestamp;
    }

    /**
     * 여러 개의 ULID를 한 번에 생성
     *
     * @param count 생성할 개수
     * @return ULID 배열
     * @throws IllegalArgumentException count가 0 이하인 경우
     */
    public static String[] generateBulk(int count) {
        if (count <= 0) {
            throw new IllegalArgumentException("Count must be positive");
        }

        String[] ulids = new String[count];
        for (int i = 0; i < count; i++) {
            ulids[i] = generate();
        }
        return ulids;
    }

    /**
     * ULID를 대문자로 정규화
     *
     * <p>ULID는 대소문자를 구분하지 않지만, 일관성을 위해 대문자로 통일합니다.
     *
     * @param ulidString ULID 문자열
     * @return 대문자로 변환된 ULID
     */
    public static String normalize(String ulidString) {
        if (ulidString == null) {
            return null;
        }
        return ulidString.toUpperCase();
    }

    /**
     * 두 ULID의 생성 시간 차이 계산 (밀리초)
     *
     * @param ulid1 첫 번째 ULID
     * @param ulid2 두 번째 ULID
     * @return 시간 차이 (밀리초), ulid1이 더 최근이면 양수
     */
    public static long getTimeDifference(String ulid1, String ulid2) {
        return getTimestamp(ulid1) - getTimestamp(ulid2);
    }

    /**
     * ULID가 특정 시간 이전에 생성되었는지 확인
     *
     * @param ulidString ULID 문자열
     * @param timestamp 비교할 Unix timestamp (밀리초)
     * @return ULID가 지정된 시간 이전에 생성된 경우 true
     */
    public static boolean isBefore(String ulidString, long timestamp) {
        return getTimestamp(ulidString) < timestamp;
    }

    /**
     * ULID가 특정 시간 이후에 생성되었는지 확인
     *
     * @param ulidString ULID 문자열
     * @param timestamp 비교할 Unix timestamp (밀리초)
     * @return ULID가 지정된 시간 이후에 생성된 경우 true
     */
    public static boolean isAfter(String ulidString, long timestamp) {
        return getTimestamp(ulidString) > timestamp;
    }
}