package kr.lunaticbum.back.lun.services

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kr.lunaticbum.back.lun.model.ImageMeta
import kr.lunaticbum.back.lun.model.ImageMetaService
import kr.lunaticbum.back.lun.model.ImageUploadResponse
import kr.lunaticbum.back.lun.utils.LogService
import net.coobird.thumbnailator.Thumbnails
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import reactor.core.publisher.Mono
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.imageio.ImageIO

@Service
class ImageService(
    private val imageMetaService: ImageMetaService,
    private val logService: LogService
) {
    @Value("\${image.upload.path}")
    private val uploadPath: String? = null

    /**
     * 이미지 파일을 읽어 HTTP 응답으로 반환합니다. (썸네일/배너 처리 포함)
     */
    suspend fun loadImage(filename: String, type: String?): ResponseEntity<ByteArray> {
        return withContext(Dispatchers.IO) {
            if (uploadPath.isNullOrBlank()) return@withContext ResponseEntity.notFound().build()

            try {
                // 1. 원본 요청인 경우
                if (type.isNullOrBlank()) {
                    return@withContext serveFile(Paths.get(uploadPath, filename), filename)
                }

                // 2. 리사이징 요청 (썸네일/배너)
                val (targetWidth, resizedFilename) = when (type) {
                    "thumbnail" -> 400 to filename.replace(".", "_thumbnail.")
                    "banner" -> 1200 to filename.replace(".", "_banner.")
                    else -> null to null
                }

                if (targetWidth == null || resizedFilename == null) {
                    return@withContext serveFile(Paths.get(uploadPath, filename), filename)
                }

                val resizedPath = Paths.get(uploadPath, resizedFilename)
                val originalPath = Paths.get(uploadPath, filename)

                // 캐시된 파일이 있으면 반환
                if (Files.exists(resizedPath)) {
                    return@withContext serveFile(resizedPath, resizedFilename)
                }

                // 원본이 없으면 404
                if (!Files.exists(originalPath)) {
                    return@withContext ResponseEntity.notFound().build()
                }

                // 리사이징 수행 후 저장
                Thumbnails.of(originalPath.toFile())
                    .width(targetWidth)
                    .keepAspectRatio(true)
                    .outputQuality(0.85)
                    .toFile(resizedPath.toFile())

                return@withContext serveFile(resizedPath, resizedFilename)

            } catch (e: IOException) {
                logService.log("Error processing image $filename: ${e.message}")
                return@withContext ResponseEntity.internalServerError().build()
            }
        }
    }

    /**
     * 업로드된 파일을 저장하고 메타데이터를 DB에 기록합니다.
     */
    suspend fun saveImage(file: MultipartFile): Mono<ImageUploadResponse> {
        return withContext(Dispatchers.IO) {
            if (uploadPath.isNullOrBlank()) {
                return@withContext Mono.just(ImageUploadResponse(1, "Upload path not configured", null))
            }
            val uniqueFilename = "${UUID.randomUUID()}_${file.originalFilename}"
            val targetPath = Paths.get(uploadPath, uniqueFilename)

            return@withContext  try {
                Files.createDirectories(targetPath.parent)
                file.transferTo(targetPath.toFile())

                // 이미지 크기 확인
                val bufferedImage = ImageIO.read(targetPath.toFile())
                val width = bufferedImage?.width ?: 0
                val height = bufferedImage?.height ?: 0

                val imageMeta = ImageMeta(
                    fileName = uniqueFilename,
                    originalFileName = file.originalFilename,
                    fileType = file.contentType,
                    fileSize = file.size,
                    width = width,
                    height = height,
                    uploadTime = System.currentTimeMillis(),
                    path = "/api/images/$uniqueFilename"
                )

                imageMetaService.save(imageMeta).map {
                    ImageUploadResponse(0, "Success", uniqueFilename)
                }
            } catch (e: Exception) {
                logService.log("Image save failed: ${e.message}")
                Mono.just(ImageUploadResponse(2, "Save failed: ${e.message}", null))
            }
        }
    }

    /**
     * 단순 파일 생성용 (내부 호출용)
     */
    fun generateThumbnailFile(originalFilename: String, targetWidth: Int) {
        if (uploadPath.isNullOrBlank()) return
        try {
            val originalFile = File(uploadPath, originalFilename)
            val thumbName = originalFilename.replace(".", "_thumbnail.")
            val thumbnailFile = File(uploadPath, thumbName)

            if (thumbnailFile.exists() || !originalFile.exists()) return

            Thumbnails.of(originalFile)
                .width(targetWidth)
                .keepAspectRatio(true)
                .toFile(thumbnailFile)
        } catch (e: IOException) {
            logService.log("Thumbnail generation failed: ${e.message}")
        }
    }

    private fun serveFile(path: Path, filename: String): ResponseEntity<ByteArray> {
        if (!Files.exists(path) || !Files.isReadable(path)) return ResponseEntity.notFound().build()

        // 보안 검사: 상위 디렉토리 접근 방지
        if (!path.normalize().startsWith(Paths.get(uploadPath!!).normalize())) {
            return ResponseEntity.badRequest().build()
        }

        val bytes = Files.readAllBytes(path)
        val contentType = when (filename.substringAfterLast('.').lowercase()) {
            "jpg", "jpeg" -> MediaType.IMAGE_JPEG
            "png" -> MediaType.IMAGE_PNG
            "gif" -> MediaType.IMAGE_GIF
            else -> MediaType.APPLICATION_OCTET_STREAM
        }

        return ResponseEntity.ok()
            .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"$filename\"")
            .contentType(contentType)
            .body(bytes)
    }
}