package com.zioinfo.cms.media;

import com.zioinfo.cms.ai.OllamaClient;
import com.zioinfo.cms.common.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * GUARDiA CMS 미디어 라이브러리 v2 — 25개 엔드포인트.
 * 파일 업로드·폴더 관리·AI 태깅·이미지 변환·공유 링크 등.
 * 온프레미스 전용 — 외부 CDN/이미지 API 절대 금지.
 */
@Slf4j
@RestController
@RequestMapping("/api/cms/media/v2")
@RequiredArgsConstructor
public class MediaLibraryController {

    private final OllamaClient ollama;
    private final MediaService mediaService; // 기존 서비스 재사용

    // ─────────── 인메모리 보조 저장소 ───────────
    private final Map<Long, Map<String, Object>> mediaItems = new ConcurrentHashMap<>();
    private final Map<Long, Map<String, Object>> folders = new ConcurrentHashMap<>();
    private final Map<String, String> shareLinks = new ConcurrentHashMap<>();   // token → mediaId
    private final AtomicLong idSeq = new AtomicLong(100);
    private long nextId() { return idSeq.getAndIncrement(); }
    private String now() { return LocalDateTime.now().toString(); }

    private Map<String, Object> buildMediaItem(String name, String type, long size, Long folderId) {
        long id = nextId();
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("id", id); m.put("name", name); m.put("type", type); m.put("size", size);
        m.put("folderId", folderId); m.put("uploadedAt", now());
        m.put("url", "/api/cms/media/v2/" + id + "/file");
        m.put("tags", new ArrayList<>()); m.put("altText", ""); m.put("shared", false);
        mediaItems.put(id, m);
        return m;
    }

    // ─────────────────────────────────────────────────────────────
    // 1. GET / — 미디어 라이브러리 목록
    // ─────────────────────────────────────────────────────────────
    @GetMapping
    public ResponseEntity<?> list(@RequestParam(required=false) Long folderId,
                                  @RequestParam(required=false) String type,
                                  @RequestParam(defaultValue="0") int page,
                                  @RequestParam(defaultValue="20") int size) {
        List<Map<String, Object>> items = mediaItems.values().stream()
            .filter(m -> folderId == null || folderId.equals(m.get("folderId")))
            .filter(m -> type == null || type.equals(m.get("type")))
            .toList();
        int total = items.size();
        int from = Math.min(page * size, total);
        int to   = Math.min(from + size, total);
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "items", items.subList(from, to), "total", total, "page", page, "size", size,
            "folders", new ArrayList<>(folders.values()))));
    }

    // ─────────────────────────────────────────────────────────────
    // 2. POST /upload — 파일 업로드
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/upload")
    public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file,
                                    @RequestParam(required=false) Long folderId) {
        if (file.isEmpty()) return ResponseEntity.ok(ApiResponse.fail("파일이 비어있습니다."));
        String contentType = file.getContentType() != null ? file.getContentType() : "application/octet-stream";
        Map<String, Object> item = buildMediaItem(file.getOriginalFilename(), contentType, file.getSize(), folderId);
        log.info("미디어 업로드: {} ({} bytes)", file.getOriginalFilename(), file.getSize());
        return ResponseEntity.ok(ApiResponse.ok(item));
    }

    // ─────────────────────────────────────────────────────────────
    // 3. POST /upload/bulk — 다중 파일 업로드
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/upload/bulk")
    public ResponseEntity<?> uploadBulk(@RequestParam("files") List<MultipartFile> files,
                                        @RequestParam(required=false) Long folderId) {
        List<Map<String, Object>> uploaded = new ArrayList<>();
        List<String> failed = new ArrayList<>();
        for (MultipartFile f : files) {
            if (f.isEmpty()) { failed.add(f.getOriginalFilename() + ": 비어있음"); continue; }
            String ct = f.getContentType() != null ? f.getContentType() : "application/octet-stream";
            uploaded.add(buildMediaItem(f.getOriginalFilename(), ct, f.getSize(), folderId));
        }
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "uploaded", uploaded, "failed", failed,
            "uploadedCount", uploaded.size(), "failedCount", failed.size())));
    }

    // ─────────────────────────────────────────────────────────────
    // 4. GET /{id} — 미디어 파일 상세
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/{id}")
    public ResponseEntity<?> getMedia(@PathVariable Long id) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        return ResponseEntity.ok(ApiResponse.ok(item));
    }

    // ─────────────────────────────────────────────────────────────
    // 5. PUT /{id}/metadata — 메타데이터 수정
    // ─────────────────────────────────────────────────────────────
    @PutMapping("/{id}/metadata")
    public ResponseEntity<?> updateMetadata(@PathVariable Long id, @RequestBody Map<String, Object> body) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        if (body.containsKey("name")) item.put("name", body.get("name"));
        if (body.containsKey("tags")) item.put("tags", body.get("tags"));
        if (body.containsKey("altText")) item.put("altText", body.get("altText"));
        if (body.containsKey("description")) item.put("description", body.get("description"));
        item.put("updatedAt", now());
        return ResponseEntity.ok(ApiResponse.ok(item));
    }

    // ─────────────────────────────────────────────────────────────
    // 6. DELETE /{id} — 파일 삭제
    // ─────────────────────────────────────────────────────────────
    @DeleteMapping("/{id}")
    public ResponseEntity<?> deleteMedia(@PathVariable Long id) {
        mediaItems.remove(id);
        return ResponseEntity.ok(ApiResponse.ok(Map.of("deleted", true, "id", id)));
    }

    // ─────────────────────────────────────────────────────────────
    // 7. DELETE /bulk — 일괄 삭제
    // ─────────────────────────────────────────────────────────────
    @DeleteMapping("/bulk")
    public ResponseEntity<?> deleteBulk(@RequestBody Map<String, Object> body) {
        @SuppressWarnings("unchecked")
        List<Object> ids = body.get("ids") instanceof List<?> l ? (List<Object>) l : List.of();
        List<Long> deleted = new ArrayList<>();
        for (Object o : ids) {
            try { Long id = Long.valueOf(String.valueOf(o)); mediaItems.remove(id); deleted.add(id); }
            catch (Exception ignored) {}
        }
        return ResponseEntity.ok(ApiResponse.ok(Map.of("deletedIds", deleted, "count", deleted.size())));
    }

    // ─────────────────────────────────────────────────────────────
    // 8. POST /folders — 폴더 생성
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/folders")
    public ResponseEntity<?> createFolder(@RequestBody Map<String, Object> body) {
        long fid = nextId();
        Map<String, Object> folder = new LinkedHashMap<>(body);
        folder.put("id", fid); folder.put("createdAt", now()); folder.put("itemCount", 0);
        folders.put(fid, folder);
        return ResponseEntity.ok(ApiResponse.ok(folder));
    }

    // ─────────────────────────────────────────────────────────────
    // 9. PUT /folders/{id} — 폴더 이름 변경
    // ─────────────────────────────────────────────────────────────
    @PutMapping("/folders/{id}")
    public ResponseEntity<?> renameFolder(@PathVariable Long id, @RequestBody Map<String, Object> body) {
        Map<String, Object> folder = folders.get(id);
        if (folder == null) return ResponseEntity.ok(ApiResponse.fail("폴더를 찾을 수 없습니다: " + id));
        folder.put("name", body.getOrDefault("name", folder.get("name")));
        folder.put("updatedAt", now());
        return ResponseEntity.ok(ApiResponse.ok(folder));
    }

    // ─────────────────────────────────────────────────────────────
    // 10. DELETE /folders/{id} — 폴더 삭제
    // ─────────────────────────────────────────────────────────────
    @DeleteMapping("/folders/{id}")
    public ResponseEntity<?> deleteFolder(@PathVariable Long id) {
        folders.remove(id);
        // 폴더 내 파일은 루트로 이동
        mediaItems.values().stream()
            .filter(m -> id.equals(m.get("folderId")))
            .forEach(m -> m.put("folderId", null));
        return ResponseEntity.ok(ApiResponse.ok(Map.of("deleted", true, "folderId", id)));
    }

    // ─────────────────────────────────────────────────────────────
    // 11. POST /{id}/move — 파일 이동
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/{id}/move")
    public ResponseEntity<?> moveMedia(@PathVariable Long id, @RequestBody Map<String, Object> body) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        Object targetFolderId = body.get("targetFolderId");
        item.put("folderId", targetFolderId); item.put("movedAt", now());
        return ResponseEntity.ok(ApiResponse.ok(Map.of("id", id, "targetFolderId", targetFolderId, "moved", true)));
    }

    // ─────────────────────────────────────────────────────────────
    // 12. POST /{id}/copy — 파일 복사
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/{id}/copy")
    public ResponseEntity<?> copyMedia(@PathVariable Long id, @RequestBody(required=false) Map<String, Object> body) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        Long targetFolder = body != null && body.get("targetFolderId") != null
            ? Long.valueOf(String.valueOf(body.get("targetFolderId"))) : null;
        String name = str(item.get("name"));
        String newName = name.contains(".") ? name.replaceFirst("(\\.[^.]+)$", "_copy$1") : name + "_copy";
        Map<String, Object> copy = buildMediaItem(newName, str(item.get("type")),
            item.get("size") instanceof Number n ? n.longValue() : 0L, targetFolder);
        return ResponseEntity.ok(ApiResponse.ok(Map.of("original", id, "copy", copy)));
    }

    // ─────────────────────────────────────────────────────────────
    // 13. POST /{id}/transform — 이미지 변환 (리사이즈·크롭·포맷)
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/{id}/transform")
    public ResponseEntity<?> transform(@PathVariable Long id, @RequestBody Map<String, Object> body) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        String op     = str(body.getOrDefault("operation", "resize"));
        Integer width = body.get("width") instanceof Number n ? n.intValue() : null;
        Integer height= body.get("height") instanceof Number n ? n.intValue() : null;
        String format = str(body.getOrDefault("format", "webp"));
        // 실제 운영 시 thumbnailator/Imgscalr 라이브러리 사용
        String newName = str(item.get("name")).replaceFirst("(\\.[^.]+)$", "_" + op + "_" + width + "x" + height + "." + format);
        Map<String, Object> result = buildMediaItem(newName, "image/" + format,
            item.get("size") instanceof Number n ? n.longValue() / 2 : 0L, null);
        result.put("transformation", Map.of("op", op, "width", width, "height", height, "format", format));
        return ResponseEntity.ok(ApiResponse.ok(Map.of("original", id, "transformed", result, "operation", op)));
    }

    // ─────────────────────────────────────────────────────────────
    // 14. GET /{id}/variants — 이미지 변형 목록 (섬네일 등)
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/{id}/variants")
    public ResponseEntity<?> getVariants(@PathVariable Long id) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        String baseName = str(item.get("name")).replaceFirst("(\\.[^.]+)$", "");
        List<Map<String, Object>> variants = List.of(
            Map.of("variant", "thumbnail", "width", 150, "height", 150, "url", "/media/" + baseName + "_thumb.webp"),
            Map.of("variant", "small", "width", 400, "height", 300, "url", "/media/" + baseName + "_sm.webp"),
            Map.of("variant", "medium", "width", 800, "height", 600, "url", "/media/" + baseName + "_md.webp"),
            Map.of("variant", "large", "width", 1200, "height", 900, "url", "/media/" + baseName + "_lg.webp"));
        return ResponseEntity.ok(ApiResponse.ok(Map.of("mediaId", id, "variants", variants)));
    }

    // ─────────────────────────────────────────────────────────────
    // 15. POST /ai-tag — AI 자동 태깅 (Ollama llava)
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/ai-tag")
    public ResponseEntity<?> aiTag(@RequestBody Map<String, Object> body) {
        String imageBase64 = str(body.getOrDefault("imageBase64", ""));
        Long mediaId       = body.get("mediaId") instanceof Number n ? n.longValue() : null;
        String raw = imageBase64.isBlank()
            ? ollama.generate("파일 " + mediaId + "의 콘텐츠 태그를 5개 추천해줘. 쉼표로 구분.")
            : ollama.vision("이미지를 설명하는 태그 5개를 쉼표로 구분해줘.", imageBase64);
        List<String> tags = raw.isBlank()
            ? List.of("이미지", "콘텐츠", "미디어", "사진", "그래픽")
            : Arrays.stream(raw.split("[,\\n]")).map(String::trim).filter(s -> !s.isBlank()).limit(10).toList();
        if (mediaId != null) {
            Map<String, Object> item = mediaItems.get(mediaId);
            if (item != null) item.put("tags", tags);
        }
        return ResponseEntity.ok(ApiResponse.ok(Map.of("mediaId", mediaId, "tags", tags, "count", tags.size())));
    }

    // ─────────────────────────────────────────────────────────────
    // 16. POST /ai-alt-text — AI 대체 텍스트 생성
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/ai-alt-text")
    public ResponseEntity<?> aiAltText(@RequestBody Map<String, Object> body) {
        String imageBase64 = str(body.getOrDefault("imageBase64", ""));
        Long mediaId       = body.get("mediaId") instanceof Number n ? n.longValue() : null;
        String fileName    = mediaId != null && mediaItems.containsKey(mediaId)
            ? str(mediaItems.get(mediaId).get("name")) : str(body.getOrDefault("fileName", "image.jpg"));
        String raw = imageBase64.isBlank()
            ? ollama.generate("파일명 '" + fileName + "'의 이미지에 대한 alt 텍스트를 100자 이내로 작성해줘.")
            : ollama.vision("이미지에 대한 alt 텍스트를 100자 이내 한국어로 작성해줘.", imageBase64);
        String altText = raw.isBlank() ? fileName.replaceAll("\\.[^.]+$", "").replace("-", " ") : raw;
        if (altText.length() > 125) altText = altText.substring(0, 122) + "...";
        if (mediaId != null) {
            Map<String, Object> item = mediaItems.get(mediaId);
            if (item != null) item.put("altText", altText);
        }
        return ResponseEntity.ok(ApiResponse.ok(Map.of("mediaId", mediaId, "altText", altText, "charCount", altText.length())));
    }

    // ─────────────────────────────────────────────────────────────
    // 17. POST /compress — 이미지 압축
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/compress")
    public ResponseEntity<?> compressImage(@RequestBody Map<String, Object> body) {
        Long mediaId = body.get("mediaId") instanceof Number n ? n.longValue() : null;
        int quality  = body.get("quality") instanceof Number n ? n.intValue() : 80;
        Map<String, Object> item = mediaId != null ? mediaItems.get(mediaId) : null;
        long origSize = item != null && item.get("size") instanceof Number n ? n.longValue() : 100000L;
        long newSize  = (long)(origSize * quality / 100.0 * 0.9);
        int savings   = (int)((origSize - newSize) * 100 / origSize);
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "mediaId", mediaId, "quality", quality,
            "originalSize", origSize, "compressedSize", newSize,
            "savingsPercent", savings + "%",
            "note", "실제 압축은 운영 서버에서 thumbnailator/Imgscalr로 처리됩니다.")));
    }

    // ─────────────────────────────────────────────────────────────
    // 18. GET /usage/{id} — 파일 사용처 조회
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/usage/{id}")
    public ResponseEntity<?> getUsage(@PathVariable Long id) {
        // 실제 운영 시 콘텐츠 DB 참조 쿼리로 교체
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "mediaId", id,
            "usages", List.of(
                Map.of("type", "content", "id", 1, "title", "홈페이지 배너", "url", "/content/1"),
                Map.of("type", "product", "id", 5, "title", "상품 이미지", "url", "/product/5")),
            "usageCount", 2)));
    }

    // ─────────────────────────────────────────────────────────────
    // 19. GET /unused — 미사용 파일 목록
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/unused")
    public ResponseEntity<?> getUnused(@RequestParam(defaultValue="30") int olderThanDays) {
        // 실제 운영 시 콘텐츠 참조 없는 파일 DB 쿼리로 교체
        List<Map<String, Object>> unused = mediaItems.values().stream().limit(5).toList(); // 데모
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "unused", unused, "count", unused.size(), "olderThanDays", olderThanDays,
            "totalSizeBytes", unused.stream().mapToLong(m ->
                m.get("size") instanceof Number n ? n.longValue() : 0).sum())));
    }

    // ─────────────────────────────────────────────────────────────
    // 20. GET /search — 미디어 검색
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/search")
    public ResponseEntity<?> search(@RequestParam(required=false) String q,
                                    @RequestParam(required=false) String type,
                                    @RequestParam(defaultValue="0") int page,
                                    @RequestParam(defaultValue="20") int size) {
        List<Map<String, Object>> results = mediaItems.values().stream()
            .filter(m -> q == null || str(m.get("name")).toLowerCase().contains(q.toLowerCase()))
            .filter(m -> type == null || str(m.get("type")).contains(type))
            .toList();
        int total = results.size();
        int from = Math.min(page * size, total);
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "results", results.subList(from, Math.min(from + size, total)),
            "total", total, "query", q, "page", page)));
    }

    // ─────────────────────────────────────────────────────────────
    // 21. GET /stats — 미디어 통계
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/stats")
    public ResponseEntity<?> getStats() {
        long totalSize = mediaItems.values().stream()
            .mapToLong(m -> m.get("size") instanceof Number n ? n.longValue() : 0).sum();
        Map<String, Long> byType = new LinkedHashMap<>();
        mediaItems.values().forEach(m -> byType.merge(str(m.get("type")), 1L, Long::sum));
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "totalFiles", mediaItems.size(), "totalFolders", folders.size(),
            "totalSizeBytes", totalSize, "totalSizeMB", totalSize / 1024 / 1024,
            "byType", byType, "generatedAt", now())));
    }

    // ─────────────────────────────────────────────────────────────
    // 22. POST /import/url — URL로 미디어 가져오기
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/import/url")
    public ResponseEntity<?> importFromUrl(@RequestBody Map<String, Object> body) {
        String url      = str(body.getOrDefault("url", ""));
        Long folderId   = body.get("folderId") instanceof Number n ? n.longValue() : null;
        if (url.isBlank()) return ResponseEntity.ok(ApiResponse.fail("URL이 필요합니다."));
        String fileName = url.contains("/") ? url.substring(url.lastIndexOf('/') + 1) : "imported_file";
        if (fileName.isBlank() || !fileName.contains(".")) fileName = "imported_" + System.currentTimeMillis() + ".jpg";
        // 실제 운영 시 URL 다운로드 후 저장
        Map<String, Object> item = buildMediaItem(fileName, "image/jpeg", 50000L, folderId);
        item.put("sourceUrl", url);
        return ResponseEntity.ok(ApiResponse.ok(Map.of("imported", item, "sourceUrl", url)));
    }

    // ─────────────────────────────────────────────────────────────
    // 23. GET /recent — 최근 업로드 미디어
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/recent")
    public ResponseEntity<?> getRecent(@RequestParam(defaultValue="10") int limit) {
        List<Map<String, Object>> recent = new ArrayList<>(mediaItems.values());
        Collections.reverse(recent);
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "recent", recent.stream().limit(limit).toList(), "limit", limit)));
    }

    // ─────────────────────────────────────────────────────────────
    // 24. GET /shared — 공유된 미디어
    // ─────────────────────────────────────────────────────────────
    @GetMapping("/shared")
    public ResponseEntity<?> getShared() {
        List<Map<String, Object>> shared = mediaItems.values().stream()
            .filter(m -> Boolean.TRUE.equals(m.get("shared"))).toList();
        return ResponseEntity.ok(ApiResponse.ok(Map.of("shared", shared, "count", shared.size())));
    }

    // ─────────────────────────────────────────────────────────────
    // 25. POST /{id}/share — 미디어 공유 링크 생성
    // ─────────────────────────────────────────────────────────────
    @PostMapping("/{id}/share")
    public ResponseEntity<?> shareMedia(@PathVariable Long id, @RequestBody(required=false) Map<String, Object> body) {
        Map<String, Object> item = mediaItems.get(id);
        if (item == null) return ResponseEntity.ok(ApiResponse.fail("미디어를 찾을 수 없습니다: " + id));
        String token = UUID.randomUUID().toString().replace("-", "").substring(0, 16);
        int expiresInHours = body != null && body.get("expiresInHours") instanceof Number n ? n.intValue() : 24;
        shareLinks.put(token, String.valueOf(id));
        item.put("shared", true); item.put("shareToken", token);
        String shareUrl = "/api/cms/media/v2/shared/" + token;
        return ResponseEntity.ok(ApiResponse.ok(Map.of(
            "mediaId", id, "shareUrl", shareUrl, "token", token,
            "expiresInHours", expiresInHours, "createdAt", now())));
    }

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