package incheon.ags.pss.edit.web;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.List;
import java.util.Locale;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.util.StringUtils;
import java.util.Set;

import de.huxhorn.sulky.ulid.ULID;
import incheon.ags.pss.edit.service.FileUploadHistoryService;
import incheon.ags.pss.edit.service.PlanImageService;
import incheon.ags.pss.edit.vo.FileUploadHistoryVO;
import incheon.ags.pss.edit.vo.PlanImageVO;
import incheon.com.cmm.api.DefaultApiResponse;
import incheon.com.cmm.ResponseCode;
import incheon.com.cmm.context.RequestContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import incheon.ags.pss.edit.util.UploadFileValidator;

/**
 * 안건지도 평면도 관리 컨트롤러
 * @author hj
 */
@Controller
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/pss/edit/planimage")
public class PlanImageController {

    private final PlanImageService planImageService;
    private final FileUploadHistoryService historyService;
    
    @Value("${Globals.pss.upload.path}")
    private String uploadPath;

    private Path absoluteUploadPath;
    
    private final ULID ulid = new ULID();
    
    @PostConstruct
    public void init() {
        this.absoluteUploadPath = Paths.get(uploadPath).toAbsolutePath().normalize();
        log.info("평면도 업로드 절대 경로: {}", this.absoluteUploadPath);
    }
    
    @GetMapping("/list.do")
    public ResponseEntity<DefaultApiResponse> selectImageList(Long bizNo) throws Exception {
    	List<PlanImageVO> list = planImageService.selectImageList(bizNo);
         
         return ResponseEntity.ok(
                 DefaultApiResponse.success(list, "조회되었습니다.")
             );
    }
    
    @PostMapping("/save.do")
    public ResponseEntity<DefaultApiResponse> insertImage(PlanImageVO vo) throws Exception {
        // 업로드 없이 flpth/trgtUrl만으로 저장하는 우회 경로 방지: 로컬 파일 시그니처 검증
        try {
            validateReferencedPlanImage(vo);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(
                    DefaultApiResponse.error(ResponseCode.INPUT_ERROR.getCode(), e.getMessage(), "INVALID_FILE_CONTENT")
            );
        }

         planImageService.insertImage(vo);
         
         return ResponseEntity.ok(
                 DefaultApiResponse.success(vo.getImgNo(), "저장되었습니다.")
             );
    }
    
    @PostMapping("/saveWithFile.do")
    public ResponseEntity<DefaultApiResponse> saveWithFile(
            @RequestParam("file") MultipartFile file,
            PlanImageVO vo // @ModelAttribute
        ) throws Exception {
        
        log.info("평면도(파일+메타) 저장: bizNo={}, imgNm={}", vo.getBizNo(), vo.getImgNm());
        
        try {
            // 허용 이미지 확장자만 업로드 가능
            String ext = UploadFileValidator.requireAllowedExtension(file, Set.of("png", "jpg", "jpeg"));
            // 확장자 위변조 방지: 실제 파일 포맷(콘텐츠) 최소 검증
            UploadFileValidator.requireValidContent(file, ext);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(
                    DefaultApiResponse.error(ResponseCode.INPUT_ERROR.getCode(), e.getMessage(), "INVALID_FILE_CONTENT")
            );
        }

        // --- 1. 파일 저장 로직 (YYYY/MM) ---
        String year = String.valueOf(LocalDate.now().getYear());
        String month = String.format("%02d", LocalDate.now().getMonthValue());
        Path absoluteDirPath = absoluteUploadPath.resolve(year).resolve(month);

        if (!Files.exists(absoluteDirPath)) {
            Files.createDirectories(absoluteDirPath);
        }
        
        String orgnlFileNm = file.getOriginalFilename();
        String extension = StringUtils.getFilenameExtension(orgnlFileNm);
        String strgFileNm = ulid.nextValue().toString() +
                              (StringUtils.hasText(extension) ? "." + extension : "");
        
        Path filePath = absoluteDirPath.resolve(strgFileNm);
        file.transferTo(filePath.toFile());
        
        //DB에 저장할 상대 경로
        String relativePath = year + "/" + month + "/" + strgFileNm;
        
        // --- 2. VO에 파일 정보 및 메타데이터 매핑 ---
        vo.setFlpth(relativePath); // 상대 경로 저장
        vo.setFileNm(orgnlFileNm);
        vo.setFilesiz(file.getSize());
        vo.setLOGIN_USER_ID(RequestContext.getCurrentUserId());
        
        planImageService.insertImage(vo); 
        
        // --- 3. 파일 업로드 이력 (빌더 패턴) ---
        FileUploadHistoryVO hist = FileUploadHistoryVO.builder()
            .bizNo(vo.getBizNo())
            .fileTypeCd("PLAN_IMG")
            .orgnlFileNm(orgnlFileNm)
            .strgFileNm(strgFileNm)
            .flpth(relativePath) // 상대 경로 저장
            .filesiz(file.getSize())
            .contsType(file.getContentType())
            .uldStcd("CMPT")
            .build();
        hist.setLOGIN_USER_ID(RequestContext.getCurrentUserId());
        
        historyService.insertFileUploadHistory(hist);
        
        return ResponseEntity.ok(
                DefaultApiResponse.success(vo.getImgNo(), "평면도가 저장되었습니다.")
            );
    }

    @PostMapping("/update.do")
    public ResponseEntity<DefaultApiResponse> updateImage(PlanImageVO vo) throws Exception {
        // 파일 업로드 없이 경로만 바꿔치기 하는 케이스 방지
        try {
            validateReferencedPlanImage(vo);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(
                    DefaultApiResponse.error(ResponseCode.INPUT_ERROR.getCode(), e.getMessage(), "INVALID_FILE_CONTENT")
            );
        }

         planImageService.updateImage(vo);
         
         return ResponseEntity.ok(
                 DefaultApiResponse.success(vo, "수정되었습니다.")
             );
    }

    @PostMapping("/delete.do")
    public ResponseEntity<DefaultApiResponse> deleteImage(Long imgNo) throws Exception {
    	planImageService.deleteImage(imgNo);
         
		return ResponseEntity.ok(
				DefaultApiResponse.success(null, "삭제되었습니다.")
		);
    }

    private void validateReferencedPlanImage(PlanImageVO vo) {
        if (vo == null) return;

        // 외부 URL 업로드/등록은 허용하지 않음 (검증 우회 및 SSRF 위험)
        if (StringUtils.hasText(vo.getTrgtUrl())) {
            String trgtUrl = vo.getTrgtUrl().trim();
            if (looksLikeExternalUrl(trgtUrl)) {
                throw new IllegalArgumentException("외부 URL 기반 파일 등록은 허용되지 않습니다.");
            }

            // 내부 리소스 경로면 로컬 파일로 매핑하여 검증
            Path local = toLocalUploadPath(trgtUrl);
            String ext = inferExtension(trgtUrl, vo.getFileNm());
            UploadFileValidator.requireValidLocalFile(local, ext);
        }

        if (StringUtils.hasText(vo.getFlpth())) {
            String flpth = vo.getFlpth().trim();
            // saveWithFile.do에서 저장하는 상대경로(yyyy/MM/...) 또는 /resources/pss/... 모두 지원
            Path local = toLocalUploadPath(flpth);
            String ext = inferExtension(flpth, vo.getFileNm());
            UploadFileValidator.requireValidLocalFile(local, ext);
        }
    }

    private boolean looksLikeExternalUrl(String pathOrUrl) {
        String s = (pathOrUrl == null ? "" : pathOrUrl.trim()).toLowerCase(Locale.ROOT);
        return s.startsWith("http://") || s.startsWith("https://");
    }

    private Path toLocalUploadPath(String flpthOrUrl) {
        String s = (flpthOrUrl == null ? "" : flpthOrUrl.trim());

        // URL 형태로 전달되는 경우(/resources/pss/...) -> 상대 경로로 변환
        if (s.startsWith("/resources/pss/")) {
            s = s.substring("/resources/pss/".length());
        }
        // 히스토리/레거시 저장 형태 대응
        if (s.startsWith("/incheon-geo-platform/upload/pss/")) {
            s = s.substring("/incheon-geo-platform/upload/pss/".length());
        }

        // 선행 슬래시 제거
        while (s.startsWith("/") || s.startsWith("\\")) {
            s = s.substring(1);
        }

        Path local = absoluteUploadPath.resolve(s).normalize();
        if (!local.startsWith(absoluteUploadPath)) {
            throw new IllegalArgumentException("파일 경로가 올바르지 않습니다.");
        }
        return local;
    }

    private String inferExtension(String pathOrUrl, String fileNm) {
        String ext = StringUtils.getFilenameExtension(StringUtils.hasText(fileNm) ? fileNm : pathOrUrl);
        if (!StringUtils.hasText(ext)) {
            throw new IllegalArgumentException("파일 확장자가 없습니다.");
        }
        return ext.toLowerCase(Locale.ROOT).trim();
    }
}
