package kr.lunaticbum.back.lun.controllers.api

import com.fasterxml.jackson.databind.ObjectMapper
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kr.lunaticbum.back.lun.model.*
import kr.lunaticbum.back.lun.service.CommentService
import kr.lunaticbum.back.lun.service.PostHistoryManager
import kr.lunaticbum.back.lun.service.PostManager
import kr.lunaticbum.back.lun.utils.LogService
import kr.lunaticbum.back.lun.utils.PayloadDecoder
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.authentication.AnonymousAuthenticationToken
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.net.URLDecoder
import java.net.URLEncoder

@RestController
@RequestMapping("/blog") // API 경로 접두어
class PostApiController(
    private val postManager: PostManager,
    private val postHistoryManager: PostHistoryManager,
    private val commentService: CommentService,
    private val objectMapper: ObjectMapper,
    private val logService: LogService
) {

    // --- GET APIs (조회) ---

    @GetMapping("/rankOfViews.bjx")
    fun getRankOfViews(): Mono<ResponseEntity<PostListResponse>> {
        val authentication = SecurityContextHolder.getContext().authentication
        val isAnonymous = authentication == null || authentication is AnonymousAuthenticationToken

        val postsFlux: Flux<Post> = if (isAnonymous) {
            postManager.getTop5UniquePublishedByViews()
        } else {
            postManager.getTop5AllVersionsByViews()
        }
        return postsFlux.collectList().map { ResponseEntity.ok(PostListResponse(it)) }
    }

    @GetMapping("/recentOfPost.bjx")
    fun getRecentOfPost(): Mono<ResponseEntity<PostListResponse>> {
        val authentication = SecurityContextHolder.getContext().authentication
        val isAnonymous = authentication == null || authentication is AnonymousAuthenticationToken

        val postsFlux: Flux<Post> = if (isAnonymous) {
            postManager.getRecent5UniquePublished()
        } else {
            postManager.getRecent5AllVersions()
        }
        return postsFlux.collectList().map { ResponseEntity.ok(PostListResponse(it)) }
    }

    @GetMapping("/posts/{postId}/comments.bjx")
    fun getComments(@PathVariable postId: String): Mono<CommentResponse> {
        return commentService.getCommentsForPost(postId)
            .collectList()
            .map { comments -> CommentResponse(0, "Success", comments) }
    }

    @GetMapping("/comments/{commentId}/replies.bjx")
    fun getReplies(@PathVariable commentId: String): Mono<CommentResponse> {
        return commentService.getRepliesForComment(commentId)
            .collectList()
            .map { replies -> CommentResponse(0, "Success", replies) }
    }

    @GetMapping("/categories.bjx")
    fun getCategories(): Mono<TagResponse> {
        return postManager.findAllDistinctCategories()
            .collectList()
            .map { categories -> TagResponse(tags = categories) }
    }

    @GetMapping("/hashtags.bjx")
    fun getHashtags(): Mono<TagResponse> {
        return postManager.findAllDistinctTags()
            .collectList()
            .map { tags -> TagResponse(tags = tags) }
    }

    // --- POST/PUT/DELETE APIs (데이터 변경) ---

    @PostMapping("/post.bjx")
    @Transactional
    suspend fun savePost(
        @RequestBody rawPayload: String,
        @AuthenticationPrincipal user: UserDetails?
    ): PostSaveResponse {
        if (user == null) {
            return PostSaveResponse(401, "Authentication required", null)
        }

        val incomingPost = PayloadDecoder.decode(rawPayload, Post::class.java, objectMapper)

        // Decode contents
        incomingPost.title = URLDecoder.decode(incomingPost.title ?: "", "UTF-8")
        incomingPost.content = URLDecoder.decode(incomingPost.content ?: "", "UTF-8")
        incomingPost.category = URLDecoder.decode(incomingPost.category ?: "none", "UTF-8")
        incomingPost.tags = URLDecoder.decode(incomingPost.tags ?: "", "UTF-8")
        incomingPost.firstAddress = URLDecoder.decode(incomingPost.firstAddress ?: "", "UTF-8")
        incomingPost.modifyAddress = URLDecoder.decode(incomingPost.modifyAddress ?: "", "UTF-8")

        return if (incomingPost.id.isNullOrBlank()) {
            // New Post
            val isAdmin = user.authorities.any { it.authority == "ROLE_ADMIN" }
            val canWrite = user.authorities.any { it.authority == "ROLE_WRITE" }
            if (!isAdmin && !canWrite) {
                return PostSaveResponse(403, "Permission denied to create post", null)
            }

            incomingPost.writer = user.username
            incomingPost.writeTime = System.currentTimeMillis()
            incomingPost.modifyTime = incomingPost.writeTime

            val savedPost = postManager.save(incomingPost).awaitSingle()
            PostSaveResponse(0, "Success", PostIdData(savedPost.id!!))

        } else {
            // Edit Post
            val originalPost = postManager.findById(incomingPost.id!!).awaitSingleOrNull()
                ?: return PostSaveResponse(404, "Original post not found", null)

            val isAdmin = user.authorities.any { it.authority == "ROLE_ADMIN" }
            val isWriter = user.username == originalPost.writer
            if (!isAdmin && !isWriter) {
                return PostSaveResponse(403, "Permission denied to update post", null)
            }
            incomingPost.writer = user.username

            // Save History
            val history = PostHistory(
                postId = originalPost.id!!,
                content = originalPost.content,
                category = originalPost.category,
                tags = originalPost.tags,
                writer = originalPost.writer,
                writeTime = originalPost.writeTime,
                posting = originalPost.posting,
                firstPostLat = originalPost.firstPostLat,
                firstPostLon = originalPost.firstPostLon,
                firstAddress = originalPost.firstAddress,
                modifyAddress = originalPost.modifyAddress,
                modifyTime = originalPost.modifyTime,
                modifyLat = originalPost.modifyLat,
                modifyLon = originalPost.modifyLon,
                readCount = originalPost.readCount,
                voteCount = originalPost.voteCount,
                unlikeCount = originalPost.unlikeCount,
                isBlocked = originalPost.isBlocked,
                postType = originalPost.postType,
            )
            postHistoryManager.save(history).awaitSingle()

            // Update Post
            val updatedPost = originalPost.copy(
                title = incomingPost.title,
                content = incomingPost.content,
                posting = incomingPost.posting,
                category = incomingPost.category,
                tags = incomingPost.tags,
                modifyTime = System.currentTimeMillis(),
                writeTime = incomingPost.writeTime,
                modifyAddress = incomingPost.modifyAddress,
                modifyLat = incomingPost.modifyLat,
                modifyLon = incomingPost.modifyLon,
                writer = incomingPost.writer,
            )

            val savedPost = postManager.save(updatedPost).awaitSingle()
            PostSaveResponse(0, "Success", PostIdData(savedPost.id!!))
        }
    }

    @DeleteMapping("/post/{postId}")
    suspend fun deletePost(
        @PathVariable postId: String,
        @AuthenticationPrincipal user: UserDetails?
    ): ResponseEntity<Map<String, String>> {
        if (user == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(mapOf("message" to "인증이 필요합니다."))
        }
        val post = postManager.findById(postId).awaitSingleOrNull()
            ?: return ResponseEntity.status(HttpStatus.NOT_FOUND).body(mapOf("message" to "삭제할 게시물을 찾을 수 없습니다."))

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

        if (!isAdmin && !isWriter) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(mapOf("message" to "이 게시물을 삭제할 권한이 없습니다."))
        }

        return try {
            postManager.deletePost(postId).awaitFirstOrNull()
            ResponseEntity.ok(mapOf("message" to "게시물이 성공적으로 삭제되었습니다."))
        } catch (e: Exception) {
            logService.log("게시물 삭제 중 오류 발생: ID=$postId, 오류=${e.message}")
            ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(mapOf("message" to "삭제 중 서버 오류가 발생했습니다."))
        }
    }

    @PostMapping("/post/{postId}/block")
    @PreAuthorize("hasRole('ADMIN')")
    fun blockPost(@PathVariable postId: String): Mono<ResponseEntity<Post>> {
        return postManager.blockPost(postId)
            .map { ResponseEntity.ok(it) }
            .defaultIfEmpty(ResponseEntity.notFound().build())
    }

    @PostMapping("/post/{postId}/unblock")
    @PreAuthorize("hasRole('ADMIN')")
    fun unblockPost(@PathVariable postId: String): Mono<ResponseEntity<Post>> {
        return postManager.unblockPost(postId)
            .map { ResponseEntity.ok(it) }
            .defaultIfEmpty(ResponseEntity.notFound().build())
    }

    @PostMapping("/posts/{postId}/comments.bjx")
    fun addComment(
        @PathVariable postId: String,
        @RequestBody rawPayload: String,
        @AuthenticationPrincipal user: UserDetails?
    ): Mono<CommentResponse> {
        val comment = PayloadDecoder.decode(rawPayload, Comment::class.java, objectMapper)
        comment.postId = postId
        comment.writer = user?.username ?: "Anonymous"
        comment.writeTime = System.currentTimeMillis()

        return commentService.addComment(comment)
            .map { CommentResponse(0, "Success") }
    }

    @PostMapping("/post/{postId}/like.bjx")
    fun likePost(@PathVariable postId: String): Mono<VoteResponse> {
        return postManager.incrementVote(postId).map { post ->
            VoteResponse(post.voteCount, post.unlikeCount)
        }
    }

    @PostMapping("/post/{postId}/unlike.bjx")
    fun unlikePost(@PathVariable postId: String): Mono<VoteResponse> {
        return postManager.incrementUnlike(postId).map { post ->
            VoteResponse(post.voteCount, post.unlikeCount)
        }
    }

    // --- Special APIs ---

    @PostMapping("/gibberish") // 경로가 /gibberish 인데 @RequestMapping("/blog") 아래에 있어서 실제로는 /blog/gibberish 가 됨.
    // 기존 코드에서는 /gibberish로 되어 있었으므로, 여기서는 RequestMapping을 오버라이드 해야 할 수도 있습니다.
    // 하지만 일관성을 위해 /blog/gibberish로 사용하거나, 아래와 같이 절대 경로로 지정합니다.
    fun saveGibberish(
        @RequestBody request: GibberishRequest,
        @AuthenticationPrincipal user: UserDetails?
    ): Mono<ResponseEntity<Any>> {
        if (user == null) {
            return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(mapOf("message" to "로그인이 필요합니다.")))
        }
        if (request.content.isBlank() || request.content.length > 100) {
            return Mono.just(ResponseEntity.badRequest().body(mapOf("message" to "내용은 1자 이상 100자 이하로 작성해야 합니다.")))
        }

        val newPost = Post(
            title = URLEncoder.encode(request.content.take(20), "UTF-8"),
            content = URLEncoder.encode(request.content, "UTF-8"),
            writer = user.username,
            writeTime = System.currentTimeMillis(),
            modifyTime = System.currentTimeMillis(),
            posting = true,
            postType = PostType.GIBBERISH.name
        )

        return postManager.save(newPost)
            .map { savedPost -> ResponseEntity.status(HttpStatus.CREATED).body(savedPost as Any) }
    }
}