package egovframework.util.excel;

import java.lang.reflect.Field;
import java.util.*;

import static egovframework.util.excel.SuperClassReflectionUtils.getAllFields;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;

/**
 * 엑셀 메타데이터 생성을 위한 팩토리 클래스 (싱글톤)
 *
 * <p>이 클래스는 VO 클래스의 {@link ExcelColumn} 및 {@link ExcelSheet} 어노테이션을 분석하여
 * 엑셀 파일 생성에 필요한 메타데이터를 생성합니다.</p>
 *
 * <p><b>주요 기능:</b></p>
 * <ul>
 *   <li>리플렉션을 사용하여 VO 클래스의 필드 정보 추출</li>
 *   <li>ExcelColumn 어노테이션에서 헤더명과 컬럼 너비 수집</li>
 *   <li>ExcelSheet 어노테이션에서 시트명 추출</li>
 *   <li>싱글톤 패턴으로 인스턴스 재사용</li>
 * </ul>
 *
 * <p><b>사용 예제:</b></p>
 * <pre>{@code
 * ExcelMetadata metadata = ExcelMetadataFactory.getInstance()
 *     .createMetadata(UserVO.class);
 *
 * String sheetName = metadata.getSheetName();
 * List<String> fieldNames = metadata.getDataFieldNames();
 * }</pre>
 *
 * @see ExcelMetadata
 * @see ExcelColumn
 * @see ExcelSheet
 * @author eGovFrame
 */
public class ExcelMetadataFactory {

	// ==================== 싱글톤 구현 ====================

	/**
	 * private 생성자 (외부에서 인스턴스 생성 방지)
	 */
	private ExcelMetadataFactory() {
	}

	/**
	 * 싱글톤 인스턴스를 보유하는 내부 클래스
	 * <p>Bill Pugh Singleton 패턴 사용 (Thread-safe, Lazy Loading)</p>
	 */
	private static class SingletonHolder {
		private static final ExcelMetadataFactory INSTANCE = new ExcelMetadataFactory();
	}

	/**
	 * 싱글톤 인스턴스를 반환합니다.
	 *
	 * @return ExcelMetadataFactory 싱글톤 인스턴스
	 */
	public static ExcelMetadataFactory getInstance() {
		return SingletonHolder.INSTANCE;
	}

	// ==================== 메타데이터 생성 ====================

	/**
	 * VO 클래스로부터 엑셀 메타데이터를 생성합니다.
	 *
	 * <p>이 메서드는 VO 클래스의 모든 필드를 검사하여 {@link ExcelColumn} 어노테이션이 붙은
	 * 필드만 추출하고, 각 필드의 헤더명과 컬럼 너비 정보를 수집합니다.</p>
	 *
	 * <p><b>처리 과정:</b></p>
	 * <ol>
	 *   <li>상속 계층 구조를 포함한 모든 필드 탐색</li>
	 *   <li>ExcelColumn 어노테이션이 있는 필드만 선택</li>
	 *   <li>헤더명, 컬럼 너비, 필드명 수집</li>
	 *   <li>ExcelSheet 어노테이션에서 시트명 추출</li>
	 *   <li>ExcelMetadata 객체 생성 및 반환</li>
	 * </ol>
	 *
	 * @param clazz 메타데이터를 생성할 VO 클래스
	 * @return 생성된 엑셀 메타데이터
	 * @throws RuntimeException ExcelColumn 어노테이션이 하나도 없는 경우
	 */
	public ExcelMetadata createMetadata(Class<?> clazz) {
		// 헤더명, 컬럼 너비, 필드명을 저장할 컬렉션 (순서 유지)
		Map<String, String> headerNamesMap = new LinkedHashMap<>();
		Map<String, Integer> headerWidthsMap = new LinkedHashMap<>();
		List<String> dataFieldNamesList = new ArrayList<>();

		// 모든 필드를 탐색하여 ExcelColumn 어노테이션 정보 수집
		for (Field field : getAllFields(clazz)) {
			if (field.isAnnotationPresent(ExcelColumn.class)) {
				ExcelColumn columnAnnotation = field.getAnnotation(ExcelColumn.class);
				String fieldName = field.getName();

				// 헤더명, 컬럼 너비, 필드명 저장
				headerNamesMap.put(fieldName, Objects.requireNonNull(columnAnnotation).headerName());
				headerWidthsMap.put(fieldName, columnAnnotation.headerWidth());
				dataFieldNamesList.add(fieldName);
			}
		}

		// ExcelColumn 어노테이션이 하나도 없으면 예외 발생
		validateHasExcelColumns(clazz, headerNamesMap);

		// 메타데이터 객체 생성 및 반환
		return new ExcelMetadata(
			headerNamesMap,
			headerWidthsMap,
			dataFieldNamesList,
			extractSheetName(clazz)
		);
	}

	// ==================== private 헬퍼 메서드 ====================

	/**
	 * ExcelColumn 어노테이션이 하나 이상 있는지 검증합니다.
	 *
	 * @param clazz 검증할 클래스
	 * @param headerNamesMap 수집된 헤더명 맵
	 * @throws RuntimeException ExcelColumn 어노테이션이 하나도 없는 경우
	 */
	private void validateHasExcelColumns(Class<?> clazz, Map<String, String> headerNamesMap) {
		if (headerNamesMap.isEmpty()) {
			throw new RuntimeException(
				String.format("클래스 %s에 @ExcelColumn 어노테이션이 하나도 없습니다.", clazz.getName())
			);
		}
	}

	/**
	 * 클래스에서 시트명을 추출합니다.
	 *
	 * <p>{@link ExcelSheet} 어노테이션이 있으면 해당 시트명을 사용하고,
	 * 없으면 기본값 "Sheet1"을 반환합니다.</p>
	 *
	 * @param clazz 시트명을 추출할 클래스
	 * @return 시트명 (ExcelSheet 어노테이션의 name 또는 "Sheet1")
	 */
	private String extractSheetName(Class<?> clazz) {
		ExcelSheet annotation = getAnnotation(clazz, ExcelSheet.class);
		return annotation != null ? annotation.name() : "Sheet1";
	}
}
