package kr.lunaticbum.back.lun.model

import com.fasterxml.jackson.databind.ObjectMapper
import kr.lunaticbum.back.lun.configs.core.GlobalEnvironment
import kr.lunaticbum.back.lun.repository.PostHistoryRepository
import kr.lunaticbum.back.lun.utils.LogService
import lombok.AllArgsConstructor
import lombok.Data
import lombok.Getter
import lombok.NoArgsConstructor
import org.bson.BsonType
import org.bson.codecs.pojo.annotations.BsonId
import org.bson.codecs.pojo.annotations.BsonIgnore
import org.bson.codecs.pojo.annotations.BsonRepresentation
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.annotation.Id
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.mongodb.core.FindAndModifyOptions // [추가됨]
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.core.query.Criteria

import org.springframework.data.mongodb.core.query.Update
import org.springframework.data.mongodb.repository.Aggregation
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.net.URLDecoder
import java.time.Duration

import org.springframework.data.mongodb.core.index.CompoundIndex // [신규 추가]
import org.springframework.data.mongodb.core.index.IndexDirection // [신규 추가]
import org.springframework.data.mongodb.core.index.Indexed // [신규 추가]
import org.springframework.data.mongodb.core.query.Query
import org.springframework.security.core.userdetails.UserDetails
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Base64
import java.util.Date

@Document(collection = "PostHistory")
data class PostHistory(
    @Id
    var id: String? = null,
    var postId: String, // 원본 Post의 ID

    // --- Post의 모든 필드를 그대로 가져옵니다 ---
    var title : String? = null,
    var content : String? = null,
    var category : String? = null,
    var tags : String? = null,
    var writer : String? = null,
    var writeTime : Long = 0,
    var posting : Boolean = false,
    var firstPostLat : Double = 0.0,
    var firstPostLon : Double = 0.0,
    var firstAddress : String = "",
    var modifyAddress : String = "",
    var modifyTime : Long = 0,
    var modifyLat : Double = 0.0,
    var modifyLon : Double = 0.0,
    var readCount : Long = 0,
    var voteCount : Long = 0,
    var unlikeCount : Long = 0,
    var isBlocked: Boolean = false,
    var postType: String = PostType.STANDARD.name,

    // --- 히스토리 전용 필드 ---
    var archivedAt: Long = System.currentTimeMillis() // 보관된 시간
)



enum class PostType {
    STANDARD, // 일반 블로그 글
    ABOUT_SITE, // 사이트 소개 글
    GIBBERISH
}

@Document(collection = "Post")
@CompoundIndex(name = "origin_time_desc_idx", def = "{'originId': 1, 'modifyTime': -1}")
data class Post(
    @BsonId
    @BsonRepresentation(BsonType.OBJECT_ID)
    var id: String? = null,

    var originId: String? = null,

    var title : String? = null,
    var content : String? = null,
    var category : String? = null,
    var tags : String? = null,

    var html : String? = null,
    var image : String? = null,
    var thumb : String? = null,

    var writer : String? = null,
    var writeTime : Long = 0,
    var posting : Boolean = false,
    var firstPostLat : Double = 0.0,
    var firstPostLon : Double = 0.0,
    var firstAddress : String = "",
    var modifyAddress : String = "",

    @Indexed(direction = IndexDirection.DESCENDING)
    var modifyTime : Long = 0,
    var modifyLat : Double = 0.0,
    var modifyLon : Double = 0.0,

    @Indexed(direction = IndexDirection.DESCENDING)
    var readCount : Long = 0,
    var voteCount : Long = 0,
    var unlikeCount : Long = 0,
    var isBlocked: Boolean = false,
    // [추가] 게시물 타입을 구분하는 필드. 기본값은 'STANDARD'
    var postType: String = PostType.STANDARD.name
)

@Document(collection = "Comment")
data class Comment (
    @BsonId
    var id: String? = null,
    var postId: String? = null     ,       // 댓글이 달린 포스트의 id
    var parentId: String? = null  ,// 대댓글이면 상위 댓글의 id, 최상위 댓글이면 null
    var writer: String? = null,
    var content: String? = null,
    var writeTime: Long? = null,
    var mentions: List<String>? = null  // 언급된 유저 아이디(선택)
)

@Data
@NoArgsConstructor
@AllArgsConstructor
class TagResult {
    var resultCode: Int = 0
    var resultMsg: String = ""
    var tags: List<String>? = null
}


@Data
@NoArgsConstructor
@AllArgsConstructor
class CommentsResult {
    var resultCode: Int = 0
    var resultMsg: String = ""
    var comments: List<Comment>? = null
}

/**
 * @Aggregation의 $count 단계에서 결과를 매핑하기 위한 헬퍼 데이터 클래스입니다.
 */
data class AggregationCount(val totalCount: Long)





/**
 * common.js에서 Base64 인코딩 후 전송하는 데이터의 구조와 일치하는 DTO입니다.
 * @param data 난독화된 실제 데이터 문자열
 * @param key 데이터를 재조합하는 데 사용되는 키
 * @param type 난독화 방식을 결정하는 타입
 */
data class EncryptedPayload(
    val data: String = "",
    val key: String = "",
    val type: String = ""
)

@Getter
class RequestModel {
    var type : String? = null
    var key : String? = null
    var data : String? = null

    fun getKeyword() = key ?: ""

    fun extractData() : String {
        data?.let {
            val reqString = data?.split(GlobalEnvironment.padding(getKeyword()))
            val nb = arrayListOf<String>()
            val na = arrayListOf<String>()
            reqString?.get(0)?.replace(GlobalEnvironment.padding(getKeyword()),"")?.split("")?.toList()?.let { na.addAll(it) }
            reqString?.get(1)?.replace(GlobalEnvironment.padding(getKeyword()),"")?.split("")?.toList()?.let { nb.addAll(it) }
            val max = nb.size + na.size
            val fullData = arrayListOf<String>()
            for (idx in 0..max) { if (idx % 2 == 0) { if (nb.size > 0) { fullData.add(nb.removeLast()) } } else { if (na.size > 0) { fullData.add(na.removeLast()) } } }
            return fullData.joinToString("")
        }
        return ""
    }
}


@Getter
private class ReportModel {
    var name : String? = null
    var email : String? = null
    var message : String? = null
}

object PayloadDecoder {

    /**
     * common.js의 unformat() 함수와 대칭되는 복호화 로직입니다.
     * @param data 난독화된 데이터
     * @param key 분리 기준이 되는 키
     * @param type 복호화 방식을 결정하는 타입
     * @return 원본 데이터 문자열
     */
    private fun format(data: String, key: String, type: String): String {
        val divider = "|*-*|$key|*-*|"
        var (odd, even) = data.split(divider).let { it[0] to it[1] }

        // 타입에 따라 unformat에서 적용된 reverse()를 다시 reverse()하여 원상복구
        when (type) {
            "T1" -> odd = odd.reversed()
            "T2" -> even = even.reversed()
            "T3" -> {
                odd = odd.reversed()
                even = even.reversed()
            }
            // [추가] 예외적인 type에 대한 기본 처리(안전장치)
            else -> {
                odd = odd.reversed()
                even = even.reversed()
            }
        }

        // odd와 even 문자열을 다시 조합하여 원본 데이터 생성
        val result = StringBuilder()
        val maxLength = maxOf(odd.length, even.length)
        for (i in 0 until maxLength) {
            if (i < even.length) result.append(even[i])
            if (i < odd.length) result.append(odd[i])
        }
        return result.toString()
    }

    /**
     * Base64로 인코딩된 전체 payload를 디코딩하고, 최종적으로 원하는 객체 타입으로 변환합니다.
     * @param payload 컨트롤러가 받은 원시 Base64 문자열
     * @param clazz 변환하고자 하는 최종 클래스 타입 (예: Post::class.java)
     * @return 변환된 객체
     */
    fun <T> decode(payload: String, clazz: Class<T>, objectMapper: ObjectMapper): T {
        // 1. Base64 디코딩 -> { "data": ..., "key": ..., "type": ... } 형태의 JSON 문자열이 됨
        val b64Decoded = String(Base64.getDecoder().decode(payload))

        // 2. 외부 JSON을 EncryptedPayload DTO로 변환
        val encryptedPayload = objectMapper.readValue(b64Decoded, EncryptedPayload::class.java)

        // 3. 내부의 난독화된 데이터를 복호화하여 원본 JSON 문자열을 얻음
        val originalJson = format(encryptedPayload.data, encryptedPayload.key, encryptedPayload.type)

        // 4. 원본 JSON을 최종 목표 객체로 변환하여 반환
        return objectMapper.readValue(originalJson, clazz)
    }
}

enum class Visibility {
    PUBLIC,   // 전체 공개
    MEMBERS,  // 회원 공개
    PRIVATE   // 비공개 (나만 보기)
}

enum class MetadataStatus {
    PENDING,    // 처리 대기 중
    COMPLETED,  // 처리 완료
    FAILED      // 처리 실패
}

enum class BookmarkType {
    URL,      // 기존 웹 페이지 링크
    IMAGE,    // 하나 이상의 이미지
    VIDEO     // 하나 이상의 비디오
}
data class BookmarkImage(
    val url: String = "",
    var isVisible: Boolean = true
)

@Document(collection = "WebBookmark")
data class WebBookmark(
    @BsonId
    @BsonRepresentation(BsonType.OBJECT_ID)
    var id: String? = null,
    var userId: String, // 누가 저장했는지
    var url: String?, // 원본 페이지 URL
    // [신규] 북마크 타입 (URL, IMAGE, VIDEO 등)
    var bookmarkType: String = BookmarkType.URL.name,
    // [신규] 콘텐츠 URL 목록 (웹페이지는 1개, 이미지는 여러 개 가능)
    @Deprecated("Use images list instead")
    var contentUrls: List<String> = emptyList(), // 이전 버전과의 호환성을 위해 남겨둡니다.
    var images: List<BookmarkImage> = emptyList(), // URL과 'isVisible' 상태를 함께 저장하는 새 필드

    var title: String? = null, // 페이지 제목
    var description: String? = null, // 페이지 요약 (메타 태그)
    var thumbnailUrl: String? = null, // 페이지 썸네일 (메타 태그)
    var userComment: String? = null, // 사용자가 남긴 짧은 의견
    var tags: List<String>? = null, // 태그 (예: #kotlin, #spring)
    var savedAt: Long = System.currentTimeMillis(), // 저장 시간
    // [신규 추가] 공개 범위 필드. 기본값은 PRIVATE.
    var visibility: String = Visibility.PRIVATE.name,
    // [신규 추가] 좋아요/싫어요 카운트 필드
    var voteCount: Long = 0,
    var unlikeCount: Long = 0,
    var userSelectedImageUrl: String? = null,
    var metadataStatus: String = MetadataStatus.PENDING.name,

    // [추가] 카테고리 필드 (하나만 가질 수 있도록 String으로 설정)
    var category: String? = null
) {

    /**
     * [이 부분을 추가하세요]
     * 화면에 표시할 최종 이미지 URL을 계산하는 프로퍼티입니다.
     * @get:BsonIgnore 어노테이션으로 이 필드는 DB에 저장되지 않습니다.
     */
    @get:BsonIgnore
    val displayImageUrl: String
        get() {
            return when {
                // 1순위: 사용자가 선택한 이미지
                !userSelectedImageUrl.isNullOrBlank() -> userSelectedImageUrl!!
                // 2순위: 자동 추출된 썸네일
                !thumbnailUrl.isNullOrBlank() -> thumbnailUrl!!
                // 3순위: 콘텐츠 URL 목록의 첫 번째 이미지
                contentUrls.isNotEmpty() -> contentUrls.first()
                // 4순위: 기본 이미지
                else -> "/images/pic01.jpg"
            }
        }
}

