package incheon.com.cmm.function;

import de.huxhorn.sulky.ulid.ULID;
import incheon.ags.ias.comCd.service.ComCdService;
import incheon.com.cmm.service.EgovProperties;
import incheon.com.cmm.util.ApplicationContextProvider;
import incheon.com.file.service.ComFileService;
import incheon.com.file.vo.ComFileDtlVO;
import org.springframework.util.StringUtils;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * JSP EL Function - 공통코드 관련 함수
 *
 * <p>JSP에서 공통코드를 쉽게 사용할 수 있도록 EL Function을 제공합니다.
 *
 * <p><b>사용 예시:</b>
 * <pre>{@code
 * <%@ taglib prefix="icfn" uri="http://incheon.com/functions" %>
 *
 * <!-- 공통코드명 조회 -->
 * <td>${icfn:codeName('GNDR_CD', user.gndrCd)}</td>
 * <td>${icfn:codeName('USER_STCD', user.userStcd)}</td>
 * }</pre>
 *
 * <p><b>TLD 파일 위치:</b> {@code /WEB-INF/tld/incheon-functions.tld}
 *
 * @author Incheon City Development Team
 * @since 2025-01-15
 */
public class CodeFunction {

    /**
     * 공통코드명 조회
     *
     * <p>그룹코드와 상세코드를 받아서 코드명(cdNm)을 반환합니다.
     * <p>코드가 존재하지 않거나 오류 발생 시 "-"를 반환합니다.
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <!-- 성별코드 'M' -> '남자' -->
     * <td>${icfn:codeName('GNDR_CD', 'M')}</td>
     *
     * <!-- 사용자 상태코드 'C' -> '가능' -->
     * <td>${icfn:codeName('USER_STCD', 'C')}</td>
     *
     * <!-- 변수와 함께 사용 -->
     * <td>${icfn:codeName('GNDR_CD', user.gndrCd)}</td>
     * }</pre>
     *
     * @param groupCd 그룹코드 (예: 'GNDR_CD', 'USER_STCD')
     * @param cd 상세코드 (예: 'M', 'F', 'C', 'D')
     * @return 코드명 (cdNm), 없으면 "-"
     */
    public static String codeName(String groupCd, String cd) {
        try {
            ComCdService comCdService = ApplicationContextProvider.getBean(ComCdService.class);
            return comCdService.getCodeName(groupCd, cd);
        } catch (Exception e) {
            // ApplicationContext 초기화 안 된 경우 또는 기타 오류
            return "-";
        }
    }

    /**
     * 공통코드명 조회 (기본값 지정 가능)
     *
     * <p>코드가 존재하지 않을 때 반환할 기본값을 지정할 수 있습니다.
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <!-- 코드가 없으면 "미정" 표시 -->
     * <td>${icfn:codeNameOrDefault('GNDR_CD', user.gndrCd, '미정')}</td>
     *
     * <!-- 코드가 없으면 빈 문자열 표시 -->
     * <td>${icfn:codeNameOrDefault('USER_STCD', user.userStcd, '')}</td>
     * }</pre>
     *
     * @param groupCd 그룹코드
     * @param cd 상세코드
     * @param defaultValue 기본값 (코드가 없을 때 반환할 값)
     * @return 코드명, 없으면 defaultValue
     */
    public static String codeNameOrDefault(String groupCd, String cd, String defaultValue) {
        try {
            ComCdService comCdService = ApplicationContextProvider.getBean(ComCdService.class);
            String codeName = comCdService.getCodeName(groupCd, cd);

            // "-"는 코드가 없다는 의미이므로 defaultValue 반환
            if ("-".equals(codeName)) {
                return defaultValue != null ? defaultValue : "-";
            }

            return codeName;
        } catch (Exception e) {
            return defaultValue != null ? defaultValue : "-";
        }
    }

    // ================= 파일 관련 EL Functions =================

    private static final ULID ulid = new ULID();

    /**
     * 파일 목록 조회
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <c:forEach items="${icfn:fileList(board.atchFileId)}" var="file">
     *     <li>${file.orgnlFileNm} (${icfn:formatFileSize(file.fileSz)})</li>
     * </c:forEach>
     * }</pre>
     *
     * @param atchFileId 파일그룹ID
     * @return 파일 목록 (List<ComFileDtlVO>)
     */
    public static List<ComFileDtlVO> fileList(String atchFileId) {
        if (!StringUtils.hasText(atchFileId)) {
            return Collections.emptyList();
        }

        try {
            ComFileService comFileService = ApplicationContextProvider.getBean(ComFileService.class);
            return comFileService.selectComFileDtlList(atchFileId);
        } catch (Exception e) {
            return Collections.emptyList();
        }
    }

    /**
     * 파일 개수 조회
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * 첨부파일: ${icfn:fileCount(board.atchFileId)}개
     * }</pre>
     *
     * @param atchFileId 파일그룹ID
     * @return 파일 개수
     */
    public static int fileCount(String atchFileId) {
        if (!StringUtils.hasText(atchFileId)) {
            return 0;
        }

        List<ComFileDtlVO> files = fileList(atchFileId);
        return files != null ? files.size() : 0;
    }

    /**
     * 파일 크기 포맷팅
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * ${icfn:formatFileSize(file.fileSz)}
     * <!-- 예: "1.5 MB" -->
     * }</pre>
     *
     * @param bytes 바이트 크기
     * @return 포맷팅된 크기 문자열
     */
    public static String formatFileSize(Long bytes) {
        if (bytes == null || bytes == 0) {
            return "0 B";
        }

        String[] units = {"B", "KB", "MB", "GB"};
        int unitIndex = 0;
        double size = bytes.doubleValue();

        while (size >= 1024 && unitIndex < units.length - 1) {
            size /= 1024;
            unitIndex++;
        }

        return String.format("%.1f %s", size, units[unitIndex]);
    }

    /**
     * 파일 확장자 추출
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * [${icfn:getFileExtension(file.orgnlFileNm)}]
     * <!-- 예: "[PDF]" -->
     * }</pre>
     *
     * @param fileName 파일명
     * @return 확장자 (대문자)
     */
    public static String getFileExtension(String fileName) {
        if (fileName != null && fileName.contains(".")) {
            String[] parts = fileName.split("\\.");
            return parts[parts.length - 1].toUpperCase();
        }
        return "FILE";
    }

    /**
     * MIME 타입으로 파일 유형 추정
     *
     * @param mimeType MIME 타입
     * @return 파일 유형 문자열
     */
    public static String getFileTypeByMime(String mimeType) {
        if (mimeType == null) return "FILE";
        if (mimeType.contains("pdf")) return "PDF";
        if (mimeType.contains("word") || mimeType.contains("msword")) return "DOC";
        if (mimeType.contains("excel") || mimeType.contains("spreadsheet")) return "XLS";
        if (mimeType.contains("powerpoint") || mimeType.contains("presentation")) return "PPT";
        if (mimeType.contains("image")) return "IMG";
        if (mimeType.contains("zip") || mimeType.contains("compressed")) return "ZIP";
        if (mimeType.contains("text")) return "TXT";
        return "FILE";
    }

    /**
     * 유니크 ID 생성 (ULID)
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <div id="file_${icfn:generateUniqueId()}">
     * }</pre>
     *
     * @return 유니크 ID
     */
    public static String generateUniqueId() {
        return ulid.nextULID();
    }

    /**
     * 파일 다운로드 URL 생성
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <a href="${icfn:fileDownloadUrl(file.fileId)}">다운로드</a>
     * }</pre>
     *
     * @param fileId 파일ID
     * @return 다운로드 URL
     */
    public static String fileDownloadUrl(String fileId) {
        if (!StringUtils.hasText(fileId)) {
            return "#";
        }
        return "/api/v1/comfile/download/" + fileId;
    }

    /**
     * 파일 존재 여부 확인
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <c:if test="${icfn:hasFiles(board.atchFileId)}">
     *     <!-- 파일이 있을 때만 표시 -->
     * </c:if>
     * }</pre>
     *
     * @param atchFileId 파일그룹ID
     * @return 파일 존재 여부
     */
    public static boolean hasFiles(String atchFileId) {
        return fileCount(atchFileId) > 0;
    }

    // ================= 날짜 포맷팅 관련 EL Functions =================

    /**
     * 날짜/시간 포맷팅 (Date, LocalDateTime, LocalDate 모두 지원)
     *
     * <p>다양한 날짜 타입을 자동으로 감지하여 포맷팅합니다.
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <!-- Date 타입 -->
     * <td>${icfn:formatDateTime(board.frstRegDt, 'yyyy-MM-dd HH:mm:ss')}</td>
     *
     * <!-- LocalDateTime 타입 -->
     * <td>${icfn:formatDateTime(linkData.frstRegDt, 'yyyy-MM-dd HH:mm:ss')}</td>
     *
     * <!-- LocalDate 타입 -->
     * <td>${icfn:formatDateTime(user.birthDate, 'yyyy-MM-dd')}</td>
     *
     * <!-- 간단한 형식 -->
     * <td>${icfn:formatDateTime(board.regDt, 'yyyy-MM-dd')}</td>
     * }</pre>
     *
     * @param dateTime 날짜/시간 객체 (Date, LocalDateTime, LocalDate 등)
     * @param pattern 포맷 패턴 (예: "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd")
     * @return 포맷팅된 날짜 문자열, 값이 없으면 "-"
     */
    public static String formatDateTime(Object dateTime, String pattern) {
        if (dateTime == null) {
            return "";
        }

        try {
            // LocalDateTime 타입 처리
            if (dateTime instanceof LocalDateTime) {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
                return ((LocalDateTime) dateTime).format(formatter);
            }

            // LocalDate 타입 처리
            if (dateTime instanceof LocalDate) {
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
                return ((LocalDate) dateTime).format(formatter);
            }

            // java.util.Date 타입 처리
            if (dateTime instanceof Date) {
                SimpleDateFormat formatter = new SimpleDateFormat(pattern);
                return formatter.format((Date) dateTime);
            }

            // java.sql.Timestamp 타입 처리 (Date 상속)
            if (dateTime instanceof java.sql.Timestamp) {
                SimpleDateFormat formatter = new SimpleDateFormat(pattern);
                return formatter.format((java.sql.Timestamp) dateTime);
            }

            // String 타입인 경우 그대로 반환 (이미 포맷팅된 경우)
            if (dateTime instanceof String) {
                return (String) dateTime;
            }

            // 지원하지 않는 타입
            return dateTime.toString();

        } catch (Exception e) {
            // 포맷팅 실패 시 기본 toString() 반환
            return dateTime.toString();
        }
    }

    /**
     * 날짜/시간 포맷팅 (기본 패턴: yyyy-MM-dd HH:mm:ss)
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <td>${icfn:formatDateTime(board.frstRegDt)}</td>
     * <!-- 출력: 2025-01-15 14:30:25 -->
     * }</pre>
     *
     * @param dateTime 날짜/시간 객체
     * @return 포맷팅된 날짜 문자열 (yyyy-MM-dd HH:mm:ss)
     */
    public static String formatDateTime(Object dateTime) {
        return formatDateTime(dateTime, "yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 날짜만 포맷팅 (패턴: yyyy-MM-dd)
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <td>${icfn:formatDate(user.birthDate)}</td>
     * <!-- 출력: 2025-01-15 -->
     * }</pre>
     *
     * @param dateTime 날짜/시간 객체
     * @return 포맷팅된 날짜 문자열 (yyyy-MM-dd)
     */
    public static String formatDate(Object dateTime) {
        return formatDateTime(dateTime, "yyyy-MM-dd");
    }

    /**
     * 시간만 포맷팅 (패턴: HH:mm:ss)
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <td>${icfn:formatTime(event.startTime)}</td>
     * <!-- 출력: 14:30:25 -->
     * }</pre>
     *
     * @param dateTime 날짜/시간 객체
     * @return 포맷팅된 시간 문자열 (HH:mm:ss)
     */
    public static String formatTime(Object dateTime) {
        return formatDateTime(dateTime, "HH:mm:ss");
    }

    // ================= 파일 업로드 설정 관련 EL Functions =================

    /**
     * 허용된 파일 확장자 목록 조회
     *
     * <p>Globals.fileUpload.Extensions 설정값을 읽어서 반환합니다.
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <file:upload accept="${icfn:allowedFileExtensions()}" />
     * <!-- 출력: ".hwp,.hwpx,.doc,.xls,..." -->
     * }</pre>
     *
     * @return 허용된 확장자 목록 (쉼표 구분, 점 포함 형식: ".hwp,.doc,.pdf")
     */
    public static String allowedFileExtensions() {
        Set<String> extensions = new LinkedHashSet<>();

        try {
            // Globals.fileUpload.Extensions (EgovProperties에서 읽기)
            String egovExtensions = EgovProperties.getProperty("Globals.fileUpload.Extensions");

            if (StringUtils.hasText(egovExtensions) && !egovExtensions.equals("99")) {
                // .hwp.hwpx.doc 형식을 파싱 (점으로 시작, 점으로 구분)
                String[] parts = egovExtensions.split("\\.");
                for (String ext : parts) {
                    String trimmed = ext.trim().toLowerCase();
                    if (StringUtils.hasText(trimmed)) {
                        extensions.add("." + trimmed);
                    }
                }
            }

        } catch (Exception e) {
            // 기본 확장자 목록 반환
            return ".hwp,.hwpx,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.jpg,.jpeg,.png,.gif,.zip,.txt";
        }

        // Set을 쉼표로 연결
        return String.join(",", extensions);
    }

    /**
     * 허용된 파일 확장자 목록 조회 (사람이 읽기 좋은 형식)
     *
     * <p><b>JSP 사용 예시:</b>
     * <pre>{@code
     * <span>허용 파일: ${icfn:allowedFileExtensionsText()}</span>
     * <!-- 출력: "hwp, hwpx, doc, xls, ..." -->
     * }</pre>
     *
     * @return 허용된 확장자 목록 (쉼표 구분, 점 없는 형식)
     */
    public static String allowedFileExtensionsText() {
        String extensions = allowedFileExtensions();
        // 점 제거하고 대문자로 변환
        return extensions.replace(".", "").toUpperCase();
    }
}
