package incheon.ags.mrb.style.web;

import com.fasterxml.jackson.core.JsonProcessingException;
import incheon.ags.mrb.style.service.RecipeLayerStyleService;
import incheon.ags.mrb.style.vo.JsonStyleVO;
import incheon.ags.mrb.style.web.dto.CategoryValueDTO;
import incheon.ags.mrb.style.web.dto.CategoryValuesRequestDTO;
import incheon.ags.mrb.style.web.dto.ClassificationRequestDTO;
import incheon.ags.mrb.style.web.dto.RecipeStyleDTO;
import incheon.ags.mrb.style.web.dto.RecipeStyleSearchDTO;
import incheon.com.cmm.api.DefaultApiResponse;
import incheon.com.security.vo.LoginVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import incheon.com.cmm.service.ResultVO;
import org.jaitools.numeric.Range;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.io.IOException;
import java.util.List;

/**
 * 레시피 레이어 스타일 REST API Controller
 * - 레시피 레이어 스타일 정보 관리 REST API
 */
@Slf4j
@Tag(name = "RecipeLayerStyle", description = "레시피 레이어 스타일 관리 API")
@RestController
@RequestMapping("/ags/mrb/layers/styles")
@RequiredArgsConstructor
public class RecipeLayerStyleApiController {

    private final RecipeLayerStyleService recipeLayerStyleService;

    /**
     * 레시피 레이어 스타일 목록 조회
     *
     * @param searchDTO 검색 조건 (선택사항, 기본값 사용)
     * @return 레시피 레이어 스타일 목록 응답
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 목록 조회", description = "검색 조건에 따른 레시피 레이어 스타일 목록을 조회합니다. 작성자/수정자 정보는 제외됩니다.")
    @GetMapping
    public ResponseEntity<DefaultApiResponse<List<RecipeStyleDTO>>> getRecipeLayerStyleList(
            @Parameter(description = "검색 조건", required = false) @ModelAttribute RecipeStyleSearchDTO searchDTO,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        // DTO가 null이면 기본값으로 초기화
        if (searchDTO == null) {
            searchDTO = RecipeStyleSearchDTO.builder().build();
        }

        // 페이징 정보 초기화
        searchDTO.initializePaging();

        log.debug("검색 조건 - searchDTO: {}, userId: {}", searchDTO, loginUser.getUserId());
        log.debug("페이징 설정 - pageNumber: {}, pageSize: {}, offset: {}",
                searchDTO.getPageNumber(), searchDTO.getPageSize(), searchDTO.getFirstIndex());

        // 목록 조회 (작성자/수정자 정보 제외, userId로 본인 것만 조회)
        List<RecipeStyleDTO> styleList = recipeLayerStyleService.selectServerStyleList(searchDTO,
                loginUser.getUserId());
        int totalCount = recipeLayerStyleService.selectServerStyleListCount(searchDTO, loginUser.getUserId());
        int totalPages = (int) Math.ceil((double) totalCount / searchDTO.getPageSize());

        log.info("레시피 레이어 스타일 목록 조회 완료 - 총 {}건", totalCount);

        // DefaultApiResponse 생성 및 메타데이터 추가
        DefaultApiResponse<List<RecipeStyleDTO>> response = DefaultApiResponse.success(styleList,
                "레시피 레이어 스타일 목록 조회 성공");
        response.addMeta("totalRecords", totalCount);
        response.addMeta("pageNumber", searchDTO.getPageNumber());
        response.addMeta("pageSize", searchDTO.getPageSize());
        response.addMeta("totalPages", totalPages);

        return ResponseEntity.ok(response);
    }

    /**
     * ------이거 수정 -------
     * 레시피 레이어 스타일 상세 조회
     *
     * @param styleId 스타일 ID
     * @return 레시피 레이어 스타일 상세 정보 응답
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 상세 조회", description = "스타일 ID로 레시피 레이어 스타일 상세 정보를 조회합니다. 작성자/수정자 정보는 제외됩니다.")
    @GetMapping("/{styleId}")
    public ResponseEntity<DefaultApiResponse<RecipeStyleDTO>> getRecipeLayerStyle(
            @Parameter(description = "스타일 ID", required = true) @PathVariable Long styleId) throws Exception {

        RecipeStyleDTO style = recipeLayerStyleService.selectServerStyle(styleId);

        log.info("레시피 레이어 스타일 상세 조회 완료 - styleId: {}", styleId);
        return ResponseEntity.ok(DefaultApiResponse.success(style, "레시피 레이어 스타일 상세 조회 성공"));
    }

    /**
     * 스타일 서비스명으로 조회
     *
     * @param styleSrvcNm 스타일 서비스명
     * @return 레시피 스타일 DTO 응답
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "스타일 서비스명으로 조회", description = "스타일 서비스명으로 레시피 레이어 스타일 정보를 조회합니다. 작성자/수정자 정보는 제외됩니다.")
    @GetMapping("/by-service-name/{styleSrvcNm}")
    public ResponseEntity<DefaultApiResponse<JsonStyleVO>> getRecipeLayerStyleByServiceName(
            @Parameter(description = "스타일 서비스명", required = true) @PathVariable String styleSrvcNm) throws Exception {

        JsonStyleVO style = recipeLayerStyleService.selectServerStyleByServiceName(styleSrvcNm);

        log.info("스타일 서비스명으로 조회 완료 - styleSrvcNm: {}", styleSrvcNm);
        return ResponseEntity.ok(DefaultApiResponse.success(style, "스타일 서비스명 조회 성공"));
    }

    /**
     * 레시피 레이어 스타일 등록
     * - JSON 스타일 정보를 SLD로 변환 후 MapPrime 서버에 저장
     * - MapPrime 저장 성공 시 DB에 스타일 정보 저장
     *
     * HTTP 상태 코드:
     * - 201 Created: 스타일 등록 성공
     * - 400 Bad Request: 유효성 검증 실패
     * - 500 Internal Server Error: MapPrime 저장 실패 또는 DB 저장 실패
     *
     * @param jsonStyleVO JSON 스타일 정보
     * @return 등록 결과
     * @throws Exception 등록 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 등록", description = "새로운 레시피 레이어 스타일을 등록합니다.")
    @PostMapping
    public ResponseEntity<DefaultApiResponse<Object>> createRecipeLayerStyle(
            @Parameter(description = "JSON 스타일 정보", required = true) @Valid @RequestBody JsonStyleVO jsonStyleVO,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        String userId = loginUser.getUserId();
        ResultVO result = recipeLayerStyleService.insertLayerStyle(jsonStyleVO, userId);

        // 등록 실패 시 500 Internal Server Error 반환
        if (result.getResultCode() != 0) {
            log.error("레시피 레이어 스타일 등록 실패 - userId: {}, message: {}", userId, result.getResultMessage());
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DefaultApiResponse.error(500, result.getResultMessage(), "STYLE_INSERT_FAILED"));
        }

        log.info("레시피 레이어 스타일 등록 성공 - userId: {}", userId);
        // 등록 성공 시 201 Created 반환
        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(DefaultApiResponse.success(result.getResult(), "레시피 레이어 스타일 등록 성공"));
    }

    /**
     * 레시피 레이어 스타일 수정
     * - 기존 스타일의 styleSrvcNm 확인 후 JSON을 SLD로 변환
     * - MapPrime 서버에 스타일 업데이트
     * - MapPrime 업데이트 성공 시 DB 업데이트
     *
     * HTTP 상태 코드:
     * - 200 OK: 스타일 수정 성공
     * - 404 Not Found: 스타일을 찾을 수 없음 또는 styleSrvcNm 없음
     * - 500 Internal Server Error: MapPrime 업데이트 실패 또는 DB 업데이트 실패
     *
     * @param styleId     스타일 ID
     * @param jsonStyleVO JSON 스타일 정보
     * @return 수정 결과
     * @throws Exception 수정 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 수정", description = "기존 레시피 레이어 스타일 정보를 수정합니다.")
    @PutMapping("/{styleId}")
    public ResponseEntity<DefaultApiResponse<Object>> updateRecipeLayerStyle(
            @Parameter(description = "스타일 ID", required = true) @PathVariable Long styleId,
            @Parameter(description = "JSON 스타일 정보", required = true) @Valid @RequestBody JsonStyleVO jsonStyleVO,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        String userId = loginUser.getUserId();
        ResultVO result = recipeLayerStyleService.updateLayerStyle(styleId, jsonStyleVO, userId);

        // 수정 실패 시 적절한 HTTP 상태 코드 반환
        if (result.getResultCode() != 0) {
            String errorMsg = result.getResultMessage();
            log.error("레시피 레이어 스타일 수정 실패 - styleId: {}, userId: {}, message: {}",
                    styleId, userId, errorMsg);

            // 스타일을 찾을 수 없거나 styleSrvcNm이 없는 경우 404 Not Found
            if (errorMsg.contains("찾을 수 없습니다") || errorMsg.contains("정보가 없습니다")) {
                return ResponseEntity
                        .status(HttpStatus.NOT_FOUND)
                        .body(DefaultApiResponse.error(404, errorMsg, "STYLE_NOT_FOUND"));
            }

            // 그 외 서버 오류는 500 Internal Server Error
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DefaultApiResponse.error(500, errorMsg, "STYLE_UPDATE_FAILED"));
        }

        log.info("레시피 레이어 스타일 수정 성공 - styleId: {}, userId: {}", styleId, userId);
        return ResponseEntity.ok(DefaultApiResponse.success(result.getResult(), "레시피 레이어 스타일 수정 성공"));
    }

    /**
     * 레시피 레이어 스타일 삭제
     * - MapPrime 서버에서 스타일 삭제 (styleSrvcNm이 있는 경우)
     * - MapPrime 삭제 완료 후 DB에서 삭제
     *
     * HTTP 상태 코드:
     * - 204 No Content: 스타일 삭제 성공
     * - 404 Not Found: 스타일을 찾을 수 없음
     * - 400 Bad Request: 스타일 ID가 없음
     * - 500 Internal Server Error: DB 삭제 실패
     *
     * @param styleId 스타일 ID
     * @return 삭제 결과
     * @throws Exception 삭제 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 삭제", description = "레시피 레이어 스타일을 삭제합니다.")
    @DeleteMapping("/{styleId}")
    public ResponseEntity<DefaultApiResponse<Object>> deleteRecipeLayerStyle(
            @Parameter(description = "스타일 ID", required = true) @PathVariable Long styleId,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        ResultVO result = recipeLayerStyleService.deleteServerStyle(styleId, loginUser.getUserId());

        // 삭제 실패 시 적절한 HTTP 상태 코드 반환
        if (result.getResultCode() != 0) {
            String errorMsg = result.getResultMessage();
            log.error("레시피 레이어 스타일 삭제 실패 - styleId: {}, message: {}", styleId, errorMsg);

            // 스타일 ID가 없는 경우 400 Bad Request
            if (errorMsg.contains("필수입니다")) {
                return ResponseEntity
                        .status(HttpStatus.BAD_REQUEST)
                        .body(DefaultApiResponse.error(400, errorMsg, "INVALID_STYLE_ID"));
            }

            // 스타일을 찾을 수 없는 경우 404 Not Found
            if (errorMsg.contains("찾을 수 없습니다")) {
                return ResponseEntity
                        .status(HttpStatus.NOT_FOUND)
                        .body(DefaultApiResponse.error(404, errorMsg, "STYLE_NOT_FOUND"));
            }

            // 그 외 서버 오류는 500 Internal Server Error
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DefaultApiResponse.error(500, errorMsg, "STYLE_DELETE_FAILED"));
        }

        log.info("레시피 레이어 스타일 삭제 성공 - styleId: {}", styleId);
        // 삭제 성공 시 204 No Content 반환
        return ResponseEntity
                .status(HttpStatus.NO_CONTENT)
                .build();
    }

    /**
     * 레시피 레이어 스타일 다중 삭제
     * - 각 스타일마다 MapPrime 서버에서 삭제 (styleSrvcNm이 있는 경우)
     * - 모든 MapPrime 삭제 완료 후 DB에서 일괄 삭제
     *
     * HTTP 상태 코드:
     * - 200 OK: 스타일 다중 삭제 성공
     * - 400 Bad Request: 스타일 ID 목록이 없음
     * - 500 Internal Server Error: DB 삭제 실패
     *
     * @param styleIds 삭제할 스타일 ID 목록
     * @return 삭제 결과
     * @throws Exception 삭제 실패 시 예외 발생
     */
    @Operation(summary = "레시피 레이어 스타일 다중 삭제", description = "여러 레시피 레이어 스타일을 한번에 삭제합니다.")
    @DeleteMapping
    public ResponseEntity<DefaultApiResponse<Object>> deleteRecipeLayerStyles(
            @Parameter(description = "삭제할 스타일 ID 목록", required = true) @RequestBody List<Long> styleIds,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        ResultVO result = recipeLayerStyleService.deleteServerStyles(styleIds, loginUser.getUserId());

        // 삭제 실패 시 적절한 HTTP 상태 코드 반환
        if (result.getResultCode() != 0) {
            String errorMsg = result.getResultMessage();
            log.error("레시피 레이어 스타일 다중 삭제 실패 - styleIds: {}, message: {}", styleIds, errorMsg);

            // 스타일 ID 목록이 없는 경우 400 Bad Request
            if (errorMsg.contains("필요합니다")) {
                return ResponseEntity
                        .status(HttpStatus.BAD_REQUEST)
                        .body(DefaultApiResponse.error(400, errorMsg, "INVALID_STYLE_IDS"));
            }

            // 그 외 서버 오류는 500 Internal Server Error
            return ResponseEntity
                    .status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DefaultApiResponse.error(500, errorMsg, "STYLES_DELETE_FAILED"));
        }

        log.info("레시피 레이어 스타일 다중 삭제 성공 - styleIds: {}", styleIds);
        return ResponseEntity.ok(DefaultApiResponse.success(result.getResult(), "레시피 레이어 스타일 다중 삭제 성공"));
    }

    /**
     * 스타일 이름 중복 확인
     *
     * @param styleName 스타일 이름
     * @return 중복 확인 결과
     * @throws Exception 확인 실패 시 예외 발생
     */
    @Operation(summary = "스타일 이름 중복 확인", description = "현재 로그인한 사용자의 스타일 이름 중복 여부를 확인합니다.")
    @GetMapping("/check-duplicate")
    public ResponseEntity<DefaultApiResponse<Boolean>> checkStyleNameDuplicate(
            @Parameter(description = "스타일 이름", required = true) @RequestParam String styleName,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        String userId = loginUser.getUserId();
        boolean isDuplicate = recipeLayerStyleService.checkStyleNameDuplicate(styleName, userId);
        String message = isDuplicate ? "이미 사용 중인 스타일 이름입니다." : "사용 가능한 스타일 이름입니다.";

        return ResponseEntity.ok(DefaultApiResponse.success(isDuplicate, message));
    }

    /**
     * 스타일 타입별 조회
     *
     * @param styleTypeCd 스타일 타입 코드
     * @param wrtrId      작성자 ID (선택사항, 없으면 현재 사용자)
     * @return 스타일 타입별 목록 응답
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "스타일 타입별 조회", description = "특정 스타일 타입의 스타일을 조회합니다. 작성자 ID가 없으면 현재 로그인한 사용자의 스타일만 조회합니다. 작성자/수정자 정보는 제외됩니다.")
    @GetMapping("/type/{styleTypeCd}")
    public ResponseEntity<DefaultApiResponse<List<RecipeStyleDTO>>> getStylesByType(
            @Parameter(description = "스타일 타입 코드", required = true) @PathVariable String styleTypeCd,
            @Parameter(description = "작성자 ID (선택사항)") @RequestParam(required = false) String wrtrId,
            @AuthenticationPrincipal LoginVO loginUser) throws Exception {

        // wrtrId가 없으면 현재 로그인한 사용자 ID 사용
        String userId = (wrtrId != null && !wrtrId.isEmpty()) ? wrtrId : loginUser.getUserId();
        List<RecipeStyleDTO> styleList = recipeLayerStyleService.selectStylesByType(styleTypeCd, userId);

        log.info("스타일 타입별 조회 완료 - styleTypeCd: {}, userId: {}, 조회된 개수: {}", styleTypeCd, userId, styleList.size());
        return ResponseEntity.ok(DefaultApiResponse.success(styleList, "스타일 타입별 조회 성공"));
    }

    /**
     * JSON을 SLD로 변환
     *
     * @param jsonStyleVO JSON 스타일 정보
     * @return SLD XML 문자열
     * @throws Exception 변환 실패 시 예외 발생
     */
    @Operation(summary = "JSON을 SLD로 변환", description = "JSON 스타일 정의를 SLD XML로 변환하여 반환합니다.")
    @PostMapping(value = "/convert/json-to-sld", produces = MediaType.APPLICATION_XML_VALUE)
    public ResponseEntity<String> convertJsonToSld(
            @Parameter(description = "JSON 스타일 정보", required = true) @Valid @RequestBody JsonStyleVO jsonStyleVO)
            throws Exception {

        String sldContent = recipeLayerStyleService.convertJsonToSld(jsonStyleVO);

        log.info("JSON to SLD 변환 성공 - layerName: {}", jsonStyleVO.getLayerName());

        // XML 직접 반환
        return ResponseEntity
                .ok()
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
                .body(sldContent);
    }

    /**
     * SLD 유효성 검증
     *
     * @param sldContent SLD XML 내용
     * @return 검증 결과
     * @throws Exception 검증 실패 시 예외 발생
     */
    @Operation(summary = "SLD 유효성 검증", description = "SLD XML 내용의 유효성을 검증합니다.")
    @PostMapping("/validate-sld")
    public ResponseEntity<DefaultApiResponse<java.util.Map<String, Object>>> validateSld(
            @Parameter(description = "SLD XML 내용", required = true) @RequestBody String sldContent) throws Exception {

        java.util.Map<String, Object> validationResult = recipeLayerStyleService.validateSld(sldContent);

        log.info("SLD 유효성 검증 완료 - 결과: {}", validationResult.get("valid"));
        return ResponseEntity.ok(DefaultApiResponse.success(validationResult, "SLD 유효성 검증 완료"));
    }

    /**
     * 스타일 ID로 SLD XML 조회
     * - DB에서 스타일 ID로 styleSrvcNm 조회
     * - MapPrime 서버에서 styleSrvcNm으로 SLD XML 조회
     *
     * HTTP 상태 코드:
     * - 200 OK: SLD XML 조회 성공
     * - 404 Not Found: 스타일을 찾을 수 없음
     * - 500 Internal Server Error: MapPrime 조회 실패
     *
     * @param styleId 스타일 ID
     * @return SLD XML 문자열
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "스타일 ID로 SLD XML 조회", description = "스타일 ID로 DB에서 styleSrvcNm을 조회한 후 MapPrime 서버에서 SLD XML을 반환합니다.")
    @GetMapping(value = "/{styleId}/sld", produces = MediaType.APPLICATION_XML_VALUE)
    public ResponseEntity<String> getSldByStyleId(
            @Parameter(description = "스타일 ID", required = true) @PathVariable Long styleId) throws Exception {

        String sldXml = recipeLayerStyleService.getSldByStyleId(styleId);

        log.info("스타일 ID로 SLD XML 조회 완료 - styleId: {}", styleId);

        // XML 직접 반환
        return ResponseEntity
                .ok()
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
                .body(sldXml);
    }

    /**
     * 스타일 ID로 JSON 스타일 조회
     * DB에서 styleId로 styleSrvcNm을 조회한 후 MapPrime 서버에서 SLD XML을 가져와 JSON으로 변환하여 반환
     * 
     * 처리 흐름:
     * 1. styleId로 DB에서 스타일 정보 조회 (styleSrvcNm, spceTy 등)
     * 2. MapPrime 서버에서 SLD XML 조회
     * 3. SLD XML을 JSON 스타일 정보로 변환
     * 
     * HTTP 상태 코드:
     * - 200 OK: JSON 스타일 조회 성공
     * - 404 Not Found: 스타일 정보를 찾을 수 없음
     * - 500 Internal Server Error: 변환 실패
     * 
     * @param styleId 스타일 ID
     * @return JSON 스타일 정보
     * @throws Exception 조회 실패 시 예외 발생
     */
    @Operation(summary = "스타일 ID로 JSON 스타일 조회", description = "스타일 ID로 DB에서 스타일 정보를 조회한 후 MapPrime 서버에서 SLD XML을 가져와 JSON으로 변환하여 반환합니다.")
    @GetMapping("/{styleId}/json")
    public ResponseEntity<DefaultApiResponse<JsonStyleVO>> getJsonStyleByStyleId(
            @Parameter(description = "스타일 ID", required = true) @PathVariable Long styleId) throws Exception {

        // 1. 스타일 정보 조회 (layerName, layerType 가져오기)
        RecipeStyleDTO styleInfo = recipeLayerStyleService.selectServerStyle(styleId);

        if (styleInfo == null) {
            log.warn("스타일 정보를 찾을 수 없습니다 - styleId: {}", styleId);
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(DefaultApiResponse.error(404, "스타일 정보를 찾을 수 없습니다", "STYLE_NOT_FOUND"));
        }

        // 2. MapPrime에서 SLD XML 조회
        String sldXml = recipeLayerStyleService.getSldByStyleId(styleId);

        if (sldXml == null || sldXml.trim().isEmpty()) {
            log.warn("SLD XML을 가져올 수 없습니다 - styleId: {}", styleId);
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(DefaultApiResponse.error(404, "SLD XML을 가져올 수 없습니다", "SLD_NOT_FOUND"));
        }

        // 3. icon_info Map 파싱 (SLD 변환 전에 미리 파싱)
        java.util.Map<String, Object> iconInfoMap = null;
        if (styleInfo.getIconInfo() != null && !styleInfo.getIconInfo().trim().isEmpty()) {
            try {
                com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
                iconInfoMap = objectMapper.readValue(
                        styleInfo.getIconInfo(),
                        new com.fasterxml.jackson.core.type.TypeReference<java.util.Map<String, Object>>() {
                        });
                log.debug("icon_info 파싱 완료: {}", iconInfoMap);
            } catch (JsonProcessingException e) {
                log.error("icon_info JSON 파싱 실패", e);
                return ResponseEntity.internalServerError()
                        .body(DefaultApiResponse.error(500, "icon_info JSON 파싱 실패", "SERVER_ERROR"));
            }
        }

        // 4. SLD XML을 JSON으로 변환 (icon_info 포함)
        // layerName: styleSrvcNm 사용 (서비스명)
        String layerName = styleInfo.getStyleSrvcNm() != null ? styleInfo.getStyleSrvcNm() : styleInfo.getStyleNm();
        JsonStyleVO jsonStyle = recipeLayerStyleService.convertSldToJson(sldXml, layerName, iconInfoMap);

        if (jsonStyle == null) {
            log.warn("SLD를 JSON으로 변환할 수 없습니다 - styleId: {}", styleId);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(DefaultApiResponse.error(500, "SLD를 JSON으로 변환할 수 없습니다", "SLD_CONVERSION_FAILED"));
        }

        // 5. icon_info를 jsonStyle에도 설정 (응답에 포함)
        if (iconInfoMap != null) {
            jsonStyle.setIconInfo(iconInfoMap);
        }

        log.info("스타일 ID로 JSON 스타일 조회 완료 - styleId: {}, layerName: {}", styleId, layerName);

        return ResponseEntity.ok(DefaultApiResponse.success(jsonStyle, "JSON 스타일 조회 성공"));
    }

    @Operation(summary = "구간 분류 요청", description = "지정한 컬럼에 대해 구간 경계값을 계산합니다.")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "구간 분류 성공", content = @Content(schema = @Schema(implementation = DefaultApiResponse.class))),
            @ApiResponse(responseCode = "400", description = "요청 파라미터 오류"),
            @ApiResponse(responseCode = "404", description = "대상 컬럼 또는 테이블을 찾을 수 없음"),
            @ApiResponse(responseCode = "500", description = "서버 내부 오류")
    })
    @GetMapping("/classification")
    public ResponseEntity<DefaultApiResponse<List<Range>>> getClassification(
            @Valid ClassificationRequestDTO request) {
        List<Range> result = null;
        try {
            result = recipeLayerStyleService.getClassification(request);
        } catch (IOException e) {
            log.error("분류 값 계산 중 오류 발생 - 서버 오류", e);
            return ResponseEntity.internalServerError()
                    .body(DefaultApiResponse.error(500, "분류 값 계산 중 오류 발생 - 서버 오류", "SERVER_ERROR"));
        }
        return ResponseEntity.ok(DefaultApiResponse.success(result, "구간 분류 성공"));
    }

    @Operation(summary = "유형별 분류: 고유값 조회", description = "지정한 컬럼의 고유값 목록을 조회합니다 (GROUP BY).")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "고유값 조회 성공", content = @Content(schema = @Schema(implementation = DefaultApiResponse.class))),
            @ApiResponse(responseCode = "400", description = "요청 파라미터 오류"),
            @ApiResponse(responseCode = "404", description = "대상 컬럼 또는 테이블을 찾을 수 없음"),
            @ApiResponse(responseCode = "500", description = "서버 내부 오류")
    })
    @GetMapping("/category")
    public ResponseEntity<DefaultApiResponse<List<CategoryValueDTO>>> getCategoryValues(
            @Valid CategoryValuesRequestDTO request) {
        try {
            log.info("유형별 분류 고유값 조회 요청 - layerId: {}, layerType: {}, columnName: {}, limit: {}, orderBy: {}",
                    request.getLayerId(), request.getLayerType(), request.getColumnName(), request.getLimit(),
                    request.getOrderBy());

            List<CategoryValueDTO> result = recipeLayerStyleService.getCategoryValues(
                    request.getLayerId(),
                    request.getLayerType(),
                    request.getColumnName(),
                    request.getLimit(),
                    request.getOrderBy());

            final int maxCategoryValues = 1000;
            final int requestedLimit = request.getLimit() == null ? maxCategoryValues : request.getLimit();
            final String responseMessage = (requestedLimit >= maxCategoryValues && result.size() == maxCategoryValues)
                    ? "고유값이 1000개를 초과하여 1000개까지만 표시합니다."
                    : "고유값 조회 성공";

            log.info("유형별 분류 고유값 조회 성공 - {} 개의 고유값 발견", result.size());
            return ResponseEntity.ok(DefaultApiResponse.success(result, responseMessage));

        } catch (IllegalArgumentException e) {
            log.error("유형별 분류 중 파라미터 오류 발생", e);
            return ResponseEntity.badRequest()
                    .body(DefaultApiResponse.error(400, e.getMessage(), "INVALID_PARAMETER"));
        } catch (Exception e) {
            log.error("유형별 분류 중 오류 발생", e);
            return ResponseEntity.internalServerError()
                    .body(DefaultApiResponse.error(500, "유형별 분류 중 오류 발생", "SERVER_ERROR"));
        }
    }

    /**
     * 랜덤 스타일 조회
     * - style_type_cd = 'R' 스타일 중 하나를 랜덤하게 가져옴
     *
     * @param styleTypeCd  스타일 타입 코드
     * @param spceTy       공간 타입
     * @param randomNumber 랜덤 숫자 (OFFSET 계산용)
     * @return 랜덤 스타일 DTO
     * @throws Exception
     */
    @Operation(summary = "랜덤 스타일 조회", description = "조건에 맞는 스타일 중 하나를 랜덤하게 조회합니다.")
    @GetMapping("/random")
    public ResponseEntity<DefaultApiResponse<RecipeStyleDTO>> getRandomStyle(
            @Parameter(description = "공간 타입", required = true) @RequestParam String spceTy,
            @Parameter(description = "스타일 번호", required = true) @RequestParam int styleNumber) throws Exception {

        log.info("랜덤 스타일 조회 요청 - spceTy: {}, styleNumber: {}", spceTy, styleNumber);

        RecipeStyleDTO style = recipeLayerStyleService.selectRandomStyle(spceTy, styleNumber);

        if (style == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(DefaultApiResponse.error(404, "조건에 맞는 랜덤 스타일을 찾을 수 없습니다.", "STYLE_NOT_FOUND"));
        }

        return ResponseEntity.ok(DefaultApiResponse.success(style, "랜덤 스타일 조회 성공"));
    }
}
