package com.zioinfo.kintex.system.loginslide;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
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.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * 로그인 슬라이드 서비스 — 공개 조회(활성·최신순 최대 5건) + 관리자 CRUD/이미지 업로드.
 * 업로드 파일은 {kintex.upload.dir}/login-slides/ 아래 UUID 파일명으로 저장하고
 * 정적 경로 /uploads/login-slides/** 로 서빙한다(WebMvcConfig). 원본 파일명은 신뢰하지 않는다.
 */
@Service
public class LoginSlideService {

    /** 공개 로테이션 최대 노출 수 — 최신 5개. */
    public static final int PUBLIC_LIMIT = 5;

    private static final Logger log = LoggerFactory.getLogger(LoginSlideService.class);
    private static final long MAX_IMAGE_BYTES = 8L * 1024 * 1024; // 8MB
    private static final Map<String, String> ALLOWED_TYPES = Map.of(
            "image/png", "png",
            "image/jpeg", "jpg",
            "image/webp", "webp");
    private static final Set<String> ALLOWED_EXTS = Set.of("png", "jpg", "jpeg", "webp");

    private final LoginSlideMapper mapper;
    private final Path uploadRoot;

    public LoginSlideService(LoginSlideMapper mapper,
                             @Value("${kintex.upload.dir:./data/uploads}") String uploadDir) {
        this.mapper = mapper;
        this.uploadRoot = Paths.get(uploadDir).toAbsolutePath().normalize();
    }

    /** 공개 — 활성 슬라이드 최신 등록순 최대 5건. */
    public List<LoginSlideDto> publicSlides() {
        return mapper.findActiveLatest(PUBLIC_LIMIT).stream().map(LoginSlideService::toDto).toList();
    }

    /** 관리자 — 전체 목록(최신 등록순). */
    public List<LoginSlideDto> adminList() {
        return mapper.findAll().stream().map(LoginSlideService::toDto).toList();
    }

    /** 관리자 — 이미지 업로드 + 문구로 신규 슬라이드 등록(등록 즉시 활성). */
    public LoginSlideDto create(MultipartFile image, String title, String caption, Integer sortOrder) {
        if (title == null || title.isBlank()) {
            throw new ApiException(ErrorCode.VALIDATION, "슬라이드 문구(타이틀)를 입력해 주세요.");
        }
        if (title.length() > 200 || (caption != null && caption.length() > 500)) {
            throw new ApiException(ErrorCode.VALIDATION, "문구 길이가 허용 범위를 초과했습니다.");
        }
        String imageUrl = storeImage(image);

        Map<String, Object> p = new HashMap<>();
        p.put("imageUrl", imageUrl);
        p.put("title", title.trim());
        p.put("caption", caption == null ? null : caption.trim());
        p.put("sortOrder", sortOrder);
        mapper.insert(p);
        long id = ((Number) p.get("id")).longValue();
        return get(id);
    }

    /** 관리자 — 문구·정렬·활성 여부 수정. */
    public LoginSlideDto update(long id, LoginSlideUpdateRequest req) {
        requireExists(id);
        Map<String, Object> p = new HashMap<>();
        p.put("id", id);
        p.put("title", req.title().trim());
        p.put("caption", req.caption() == null ? null : req.caption().trim());
        p.put("sortOrder", req.sortOrder());
        p.put("active", req.active());
        mapper.update(p);
        return get(id);
    }

    /** 관리자 — 삭제(업로드 파일도 정리, 시드/번들 자산은 보존). */
    public void delete(long id) {
        LoginSlideDto slide = get(id);
        mapper.delete(id);
        removeUploadedFile(slide.imageUrl());
    }

    public LoginSlideDto get(long id) {
        Map<String, Object> r = mapper.findById(id);
        if (r == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "슬라이드를 찾을 수 없습니다.");
        }
        return toDto(r);
    }

    private void requireExists(long id) {
        if (mapper.findById(id) == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "슬라이드를 찾을 수 없습니다.");
        }
    }

    // ── 이미지 저장 ──

    private String storeImage(MultipartFile image) {
        if (image == null || image.isEmpty()) {
            throw new ApiException(ErrorCode.VALIDATION, "슬라이드 이미지를 첨부해 주세요.");
        }
        if (image.getSize() > MAX_IMAGE_BYTES) {
            throw new ApiException(ErrorCode.VALIDATION, "이미지는 8MB 이하만 업로드할 수 있습니다.");
        }
        String ext = resolveExtension(image);
        String fileName = UUID.randomUUID() + "." + ext;
        try {
            Path dir = uploadRoot.resolve("login-slides");
            Files.createDirectories(dir);
            Path target = dir.resolve(fileName).normalize();
            if (!target.startsWith(uploadRoot)) { // 경로 탈출 방어(파일명은 UUID라 사실상 불변식)
                throw new ApiException(ErrorCode.VALIDATION, "잘못된 파일 경로입니다.");
            }
            try (var in = image.getInputStream()) {
                Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
            }
        } catch (IOException e) {
            log.error("login-slide image store failed: {}", e.getMessage());
            throw new ApiException(ErrorCode.INTERNAL, "이미지 저장에 실패했습니다.");
        }
        return "/uploads/login-slides/" + fileName;
    }

    private static String resolveExtension(MultipartFile image) {
        String byType = image.getContentType() == null
                ? null : ALLOWED_TYPES.get(image.getContentType().toLowerCase(Locale.ROOT));
        if (byType != null) {
            return byType;
        }
        String name = image.getOriginalFilename();
        if (name != null && name.contains(".")) {
            String ext = name.substring(name.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT);
            if (ALLOWED_EXTS.contains(ext)) {
                return "jpeg".equals(ext) ? "jpg" : ext;
            }
        }
        throw new ApiException(ErrorCode.VALIDATION, "PNG/JPG/WebP 이미지만 업로드할 수 있습니다.");
    }

    private void removeUploadedFile(String imageUrl) {
        if (imageUrl == null || !imageUrl.startsWith("/uploads/login-slides/")) {
            return; // 번들 정적 자산(시드) 등은 삭제하지 않음
        }
        String fileName = imageUrl.substring("/uploads/login-slides/".length());
        if (fileName.contains("/") || fileName.contains("\\") || fileName.contains("..")) {
            return;
        }
        try {
            Files.deleteIfExists(uploadRoot.resolve("login-slides").resolve(fileName));
        } catch (IOException e) {
            log.warn("login-slide image cleanup failed: {}", e.getMessage()); // 삭제 실패는 무해 — 로그만
        }
    }

    private static LoginSlideDto toDto(Map<String, Object> r) {
        return new LoginSlideDto(
                ((Number) r.get("id")).longValue(),
                str(r.get("imageUrl")),
                str(r.get("title")),
                str(r.get("caption")),
                r.get("sortOrder") == null ? null : ((Number) r.get("sortOrder")).intValue(),
                Boolean.TRUE.equals(r.get("active")),
                str(r.get("createdAt")),
                str(r.get("updatedAt")));
    }

    private static String str(Object o) {
        return o == null ? null : String.valueOf(o);
    }
}
