package com.zioinfo.kintex.module.m5.reroom;

import com.zioinfo.kintex.auth.profile.ProfilePhotoService;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.module.m5.reroom.dto.ReferenceImageDto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

/**
 * ReRoomAI 방식 사진 시안의 참조 이미지 저장/해석 — 소유자 지시 13.2.
 *
 * <p>저장 컨벤션은 로그인 슬라이드/아바타와 동일: {@code {kintex.upload.dir}/reroom/} + <b>서버 UUID 파일명</b>.
 * 원본 파일명·클라이언트 경로는 신뢰하지 않으므로 경로 순회는 구조적으로 불가능하다. 공개 서빙은
 * {@code /uploads/reroom/**}(WebMvcConfig 정적 핸들러). 나노바나나 워커는 같은 파일시스템의 <b>로컬 절대경로</b>로
 * 참조 이미지를 소비한다.
 *
 * <p>검증(다층): ①크기 상한(10MB) ②매직바이트 실제 유형 판별(PNG/JPG/WebP, SVG·스크립트·위조 헤더 차단,
 * {@link ProfilePhotoService#detectImageType} 재사용).
 */
@Component
public class ReRoomReferenceStore {

    private static final Logger log = LoggerFactory.getLogger(ReRoomReferenceStore.class);

    /** 참조 이미지 상한(10MB) — 프론트가 1024px/JPEG 0.85로 사전 다운스케일(ReRoomAI 전처리)하므로 실사용은 훨씬 작다. */
    private static final long MAX_IMAGE_BYTES = 10L * 1024 * 1024;

    /** 허용 선언 MIME 화이트리스트(불일치 시 매직바이트를 신뢰). */
    private static final Map<String, String> ALLOWED_TYPES = Map.of(
            "image/png", "png",
            "image/jpeg", "jpg",
            "image/webp", "webp");

    private final Path reroomDir;

    public ReRoomReferenceStore(@Value("${kintex.upload.dir:./data/uploads}") String uploadDir) {
        this.reroomDir = Paths.get(uploadDir).toAbsolutePath().normalize().resolve("reroom");
    }

    /** 참조 이미지 업로드(멀티파트) → {referenceId, url}. */
    public ReferenceImageDto store(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new ApiException(ErrorCode.VALIDATION, "참조 이미지 파일을 첨부해 주세요.");
        }
        byte[] bytes;
        try {
            bytes = file.getBytes();
        } catch (IOException e) {
            log.warn("reroom reference read failed: {}", e.getMessage());
            throw new ApiException(ErrorCode.VALIDATION, "이미지를 읽을 수 없습니다.");
        }
        if (bytes.length == 0) {
            throw new ApiException(ErrorCode.VALIDATION, "빈 이미지입니다.");
        }
        if (bytes.length > MAX_IMAGE_BYTES) {
            throw new ApiException(ErrorCode.VALIDATION, "참조 이미지는 10MB 이하만 업로드할 수 있습니다.");
        }
        // 선언 타입이 있으면 화이트리스트 여부만 확인(불일치는 매직바이트를 신뢰).
        String declared = file.getContentType();
        String normalized = declared == null ? null : declared.toLowerCase(Locale.ROOT).trim();
        if (normalized != null && !normalized.isBlank() && !ALLOWED_TYPES.containsKey(normalized)) {
            throw new ApiException(ErrorCode.VALIDATION, "PNG/JPG/WebP 이미지만 업로드할 수 있습니다.");
        }
        // 매직바이트로 실제 유형 확정 — SVG/스크립트/위조 헤더 차단.
        String detected = ProfilePhotoService.detectImageType(bytes);
        if (detected == null) {
            throw new ApiException(ErrorCode.VALIDATION, "PNG/JPG/WebP 이미지만 업로드할 수 있습니다.");
        }

        String fileName = UUID.randomUUID() + "." + detected; // 서버 생성 파일명(원본명 미신뢰)
        try {
            Files.createDirectories(reroomDir);
            Path target = reroomDir.resolve(fileName).normalize();
            if (!target.startsWith(reroomDir)) { // 파일명이 UUID라 사실상 불변식이나 방어적 확인
                throw new ApiException(ErrorCode.VALIDATION, "잘못된 파일 경로입니다.");
            }
            Files.write(target, bytes);
        } catch (IOException e) {
            log.error("reroom reference store failed: {}", e.getMessage());
            throw new ApiException(ErrorCode.INTERNAL, "이미지 저장에 실패했습니다.");
        }
        return new ReferenceImageDto(fileName, publicUrl(fileName));
    }

    /** referenceId(파일명) → 워커 소비용 로컬 절대경로. 안전성 검증 후 실재 파일만 통과. */
    public String resolveLocalPath(String referenceId) {
        String fileName = requireSafe(referenceId);
        Path target = reroomDir.resolve(fileName).normalize();
        if (!target.startsWith(reroomDir) || !Files.isRegularFile(target)) {
            throw new ApiException(ErrorCode.NOT_FOUND, "참조 이미지를 찾을 수 없습니다.");
        }
        return target.toString();
    }

    /** referenceId → 프론트 Before 표시용 공개 URL. */
    public String publicUrl(String referenceId) {
        return "/uploads/reroom/" + requireSafe(referenceId);
    }

    // 경로 순회/구분자 차단 — referenceId 는 서버가 UUID로 발급하므로 위조 시도만 거른다.
    private static String requireSafe(String name) {
        if (name == null || name.isBlank()
                || name.contains("/") || name.contains("\\") || name.contains("..")) {
            throw new ApiException(ErrorCode.VALIDATION, "잘못된 참조 이미지 식별자입니다.");
        }
        return name;
    }
}
