package kr.lunaticbum.back.lun.controllers.view

import com.fasterxml.jackson.databind.ObjectMapper
import com.google.gson.Gson
import com.google.gson.JsonParser
import jakarta.servlet.http.HttpServletResponse
import kotlinx.coroutines.reactive.awaitSingle
import kotlinx.coroutines.reactive.awaitSingleOrNull
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kr.lunaticbum.back.lun.model.*
import kr.lunaticbum.back.lun.model.dto.FeedResponse
import kr.lunaticbum.back.lun.service.FeedService
import kr.lunaticbum.back.lun.service.PostManager
import kr.lunaticbum.back.lun.services.ImageService
import kr.lunaticbum.back.lun.utils.LogService
import net.coobird.thumbnailator.Thumbnails
import org.jsoup.Jsoup
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
import java.io.File
import java.io.IOException
import java.net.URLDecoder
import java.text.SimpleDateFormat
import java.util.*

@Controller
class PostViewController(
    private val postManager: PostManager,
    private val imageMetaService: ImageMetaService,
    private val visitorLogService: VisitorLogService,
    private val objectMapper: ObjectMapper,
    private val logService: LogService,
    private val feedService: FeedService,
    private val imageService: ImageService // [주입 추가]
) {
    @Value("\${image.upload.path}")
    private val uploadPath: String? = null

    // --- Helper Methods (View 전용) ---
    private fun processPostForView(post: Post): Post {
        post.title = post.title ?: ""
        post.content = post.content ?: ""
        post.tags = post.tags ?: ""
        post.category = if (post.category.isNullOrBlank()) "none" else post.category
        post.firstAddress = post.firstAddress ?: ""
        post.modifyAddress = post.modifyAddress ?: ""

        if (post.title!!.isBlank()) {
            val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm")
            post.title = "무제(無題) [${sdf.format(Date(post.writeTime))}]"
        }

        var firstImgSrc: String? = null
        val defaultThumb = "/images/pic01.jpg"

        try {
            JsonParser.parseString(post.content)
            val (text, firstImg) = extractFromDelta(post.content!!)
            post.html = text
            firstImgSrc = firstImg
        } catch (e: Exception) {
            val doc = Jsoup.parse(post.content)
            post.html = doc.text()
            firstImgSrc = doc.select("img").first()?.attr("src")
        }

        if (!firstImgSrc.isNullOrBlank()) {
            val filename = firstImgSrc.substringAfterLast("/")
            post.image = "/api/images/$filename"

            // [변경] 서비스 메서드 호출
            imageService.generateThumbnailFile(filename, 200)

            val thumbFilename = filename.substringBeforeLast(".") + "_thumbnail." + filename.substringAfterLast(".")
            post.thumb = "/api/images/$thumbFilename?type=thumbnail"
        } else {
            post.image = null
            post.thumb = defaultThumb
        }

        return post
    }

    private data class DeltaOp(val insert: Any)
    private data class Delta(val ops: List<DeltaOp>)

    private fun extractFromDelta(deltaJson: String): Pair<String, String?> {
        val delta: Delta = Gson().fromJson(deltaJson, Delta::class.java)
        val textOnly = StringBuilder()
        var firstImage: String? = null

        delta.ops.forEach { op ->
            if (op.insert is String) {
                textOnly.append(op.insert)
            } else if (op.insert is Map<*, *> && firstImage == null) {
                val obj = op.insert as Map<*, *>
                if (obj["image"] != null) {
                    firstImage = obj["image"].toString()
                }
            }
        }
        return textOnly.toString() to firstImage
    }

    private fun generateThumbnail(originalFilename: String, targetWidth: Int) {
        if (uploadPath.isNullOrBlank() || originalFilename.isBlank()) return
        try {
            val originalFile = File(uploadPath, originalFilename)
            val thumbnailFilename = originalFilename.substringBeforeLast(".") + "_thumbnail." + originalFilename.substringAfterLast(".")
            val thumbnailFile = File(uploadPath, thumbnailFilename)

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

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

    @GetMapping("/api/feed")
    @ResponseBody
    suspend fun getFeedMore(
        @RequestParam cursor: Long,
        @RequestParam(required = false) q: String? // 더보기 할 때도 검색어 유지 필요
    ): FeedResponse {
        return feedService.getGlobalFeed(cursor, 10, q).awaitSingle()
    }
    // --- View Endpoints ---

    @GetMapping("/", "/home.bs")
    suspend fun home(
        @RequestParam(required = false) q: String?,
        request: jakarta.servlet.http.HttpServletRequest): ResultMV {
        visitorLogService.recordVisit(request).subscribe()
        val vm = ResultMV("content/home")
        val defaultBannerImage = "/api/images/0e2bf8b1-1848-4650-b084-5b52d0815be9.jpg?type=banner"

        try {
            var bannerImagePath: String? = null
            val randomImage: ImageMeta? = imageMetaService.getRandomBannerImage().awaitSingleOrNull()

            if (randomImage != null && !randomImage.path.isNullOrBlank()) {
                if (randomImage.path.contains("/blog/post/images/")) {
                    bannerImagePath = randomImage.path.replace("/blog/post/images/", "/api/images/") +"?type=banner"
                } else {
                    bannerImagePath = randomImage.path +"?type=banner"
                }
            }
            vm.modelMap["randomBannerImage"] = if (!bannerImagePath.isNullOrBlank()) bannerImagePath else defaultBannerImage

            val randomGibberish = postManager.findRandomGibberish().awaitSingleOrNull()
            if (randomGibberish != null) {
                vm.modelMap["gibberish"] = URLDecoder.decode(randomGibberish.content, "UTF-8")
                vm.modelMap["gibberishId"] = randomGibberish.id
            }
            val feedData = feedService.getGlobalFeed(null, 10, q).awaitSingle()

            vm.modelMap["feedItems"] = feedData.items

            vm.modelMap["nextCursor"] = feedData.nextCursor // HTML에 hidden으로 숨겨둠
            vm.modelMap["searchQuery"] = q // HTML에 hidden으로 숨겨둠
//            val postsList: List<Post> = postManager.find8().awaitSingleOrNull() ?: emptyList()
//            vm.modelMap["Posts"] = postsList.map { processPostForView(it) }
            vm.modelMap["path"] = "/blog/viewer/"

        } catch (ex: Exception) {
            ex.printStackTrace()
            logService.log("Error loading home page: ${ex.message}")
        }
        return vm
    }

    @GetMapping("/blog/posts")
    suspend fun postsList(
        @RequestParam(value = "page", defaultValue = "0") page: Int,
        @RequestParam(required = false) category: String?,
        @RequestParam(required = false) tag: String?,
        @AuthenticationPrincipal userDetails: UserDetails?
    ): ResultMV {
        val vm = ResultMV("content/posts")
        val pageable = PageRequest.of(page, 8)

        vm.modelMap["currentCategory"] = category
        vm.modelMap["currentTag"] = tag

        val posts: List<Post>
        val total: Long

        when {
            !category.isNullOrBlank() -> {
                posts = postManager.findPostsByCategory(category, pageable).awaitSingle()
                total = postManager.countPostsByCategory(category).awaitSingle()
                vm.modelMap["filterTitle"] = "'${category}' 카테고리의 글"
            }
            !tag.isNullOrBlank() -> {
                posts = postManager.findPostsByTag(tag, pageable).awaitSingle()
                total = postManager.countPostsByTag(tag).awaitSingle()
                vm.modelMap["filterTitle"] = "'#${tag}' 태그가 포함된 글"
            }
            else -> {
                val roles = userDetails?.authorities?.map { it.authority } ?: emptyList()
                val username = userDetails?.username
                when {
                    roles.contains("ROLE_ADMIN") -> {
                        posts = postManager.findAllVersionsPaginated(pageable).awaitSingle()
                        total = postManager.countAllVersions().awaitSingle()
                    }
                    roles.contains("ROLE_WRITE") && username != null -> {
                        posts = postManager.findLatestUniqueForWriter(username, pageable).awaitSingle()
                        total = postManager.countLatestUniqueForWriter(username).awaitSingle()
                    }
                    else -> {
                        posts = postManager.findLatestUniquePaginated(pageable).awaitSingle()
                        total = postManager.countLatestUnique().awaitSingle()
                    }
                }
            }
        }

        val processedPosts = posts.map { processPostForView(it) }
        vm.modelMap["postsPage"] = PageImpl(processedPosts, pageable, total)
        return vm
    }

    @GetMapping("/blog/viewer/{postId}")
    suspend fun postViewer(
        @PathVariable postId: String,
        @AuthenticationPrincipal userDetails: UserDetails?
    ): ResultMV {
        val vm = ResultMV("content/viewer")
        try {
            val post = postManager.getPost(postId).awaitSingleOrNull()
                ?: return ResultMV("redirect:/blog/posts")

            val isWriter = userDetails?.username == post.writer
            val isAdmin = userDetails?.authorities?.any { it.authority == "ROLE_ADMIN" } == true

            if (!post.posting && !isWriter && !isAdmin) {
                logService.log("Access denied for user ${userDetails?.username} to post ${post.id}")
                return ResultMV("redirect:/blog/posts")
            }
            val processedPost = processPostForView(post)
            vm.modelMap["srcPost"] = processedPost
            vm.modelMap["srcPostJson"] = objectMapper.writeValueAsString(processedPost)
        } catch (e: Exception) {
            return ResultMV("redirect:/")
        }
        return vm
    }

    @GetMapping(value = ["/blog/edit", "/blog/edit/{postId}"])
    suspend fun editPost(
        @PathVariable(required = false) postId: String?,
        @RequestParam(required = false) type: String?,
        @AuthenticationPrincipal userDetails: UserDetails?
    ): ResultMV {
        if (userDetails == null) {
            return ResultMV("redirect:/home.bs?action=login")
        }

        val isAdmin = userDetails.authorities.any { it.authority == "ROLE_ADMIN" }
        val canWrite = userDetails.authorities.any { it.authority == "ROLE_WRITE" }

        val vm = ResultMV("content/editor")
        try {
            if (postId == null) {
                if (!canWrite && !isAdmin) {
                    return ResultMV("redirect:/blog/posts")
                }
                vm.modelMap["pageTitle"] = "새 글 작성"

                val newPost = Post().apply {
                    title = "무제(無題) (${SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date())})"
                    content = ""
                    if (type == PostType.ABOUT_SITE.name) {
                        this.postType = PostType.ABOUT_SITE.name
                        vm.modelMap["pageTitle"] = "사이트 소개글 작성"
                    }
                }
                vm.modelMap["srcPost"] = newPost
                vm.modelMap["srcPostJson"] = objectMapper.writeValueAsString(newPost)

            } else {
                vm.modelMap["pageTitle"] = "글 수정"
                val rawPost = postManager.findById(postId).awaitSingleOrNull()
                    ?: return ResultMV("redirect:/blog/posts")

                val isWriter = userDetails.username == rawPost.writer
                if (!isAdmin && !isWriter) {
                    return ResultMV("redirect:/blog/posts")
                }

                var processedContent: String
                try {
                    processedContent = URLDecoder.decode(rawPost.content, "UTF-8")
                } catch (e: Exception) {
                    processedContent = rawPost.content ?: ""
                }
                rawPost.content = processedContent
                val processedPost = processPostForView(rawPost)

                vm.modelMap["srcPost"] = processedPost
                vm.modelMap["srcPostJson"] = objectMapper.writeValueAsString(processedPost)
            }
        } catch (e: Exception) {
            logService.log("Error processing edit page for postId: $postId. Error: ${e.message}")
            return ResultMV("redirect:/blog/posts")
        }
        return vm
    }

    @GetMapping("/login")
    fun login(response: HttpServletResponse) {
        response.sendRedirect("/user/login")
    }

    @GetMapping("/licenses")
    fun licenses() = ResultMV("content/licenses")
}