package kr.lunaticbum.back.lun.controllers

import com.drew.imaging.ImageMetadataReader
import com.drew.metadata.Metadata
import com.google.gson.Gson
import com.google.maps.GeoApiContext
import com.google.maps.GeocodingApi
import com.google.maps.model.LatLng
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.ApiKeyWordKey
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncType11
import kr.lunaticbum.back.lun.configs.GlobalEnvironment.Companion.EncTypeKey
import kr.lunaticbum.back.lun.model.*
import kr.lunaticbum.back.lun.utils.LogService
import kr.lunaticbum.back.lun.utils.getFileExtension
import net.coobird.thumbnailator.Thumbnails
import org.commonmark.node.Node
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.io.Resource
import org.springframework.core.io.UrlResource
import org.springframework.data.domain.Pageable
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.web.bind.annotation.*
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import java.io.*
import java.net.URLDecoder
import java.text.SimpleDateFormat
import java.util.*


@RestController
@RequestMapping("/blog")
class BlogController() {
    companion object {
        val TEMPTOKEN = "TEMP_TOKEN_VIBUM"
    }
    @Autowired
    lateinit var globalEvv : GlobalEnvironment

    @Autowired
    private lateinit var locationLogService: LocationLogService

    @Autowired
    private lateinit var postManager: PostManager

    @Autowired
    lateinit var logService: LogService
    val WRITE_PERMISSION_KEY = "PERMISSION"
    @GetMapping("write/{token}","write.bs")
    fun writ(@PathVariable token : String? ) : ResultMV{
        val vm = ResultMV("content/blog/write")
        if (token.equals(TEMPTOKEN)) {
            vm.modelMap.put(WRITE_PERMISSION_KEY,"OK")
            vm.modelMap.put(EncTypeKey, EncType11)
            vm.modelMap.put(ApiKeyWordKey,"WRITE")
            vm.modelMap.put("title","회원이 들어는 구나~!!")
            vm.modelMap.put("defaultTitle","무제(無題) (Untitled, ${SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date())})")
        } else {
            vm.modelMap.put(WRITE_PERMISSION_KEY,"NO")
        }
        return vm
    }

    @PostMapping("post.bjx")
    fun post(httpServletRequest: HttpServletRequest, @RequestBody jsonString: String) : ResponseEntity<ResponceResult> {
        logService.log(httpServletRequest.requestURI)
        logService.log(jsonString)
        var lResultCode = 0
        var lResultMsg = "Suscces"
        val decodedBytes: ByteArray = Base64.getDecoder().decode(jsonString)
        String(decodedBytes).let {
            Gson().fromJson<RequestModel>(it, RequestModel::class.java)?.let { model ->
                logService.log(Gson().toJson(model))
                model.data?.let {   jsonString ->
                    try {
                        val reqString = jsonString.split(GlobalEnvironment.padding(model.getKeyword()))
                        val nb = arrayListOf<String>()
                        val na = arrayListOf<String>()
                        reqString[0].replace(GlobalEnvironment.padding(model.getKeyword()),"").split("").toList().let { na.addAll(it) }
                        reqString[1].replace(GlobalEnvironment.padding(model.getKeyword()),"").split("").toList().let { nb.addAll(it) }
                        var max = nb.size + na.size
                        var 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()) } } }
                        logService.log(fullData.joinToString(""))
                        var target = Gson().fromJson(fullData.joinToString(""), Post::class.java) ?: Post()
                        if (target.writeTime < 1L) {
                            target.id = null
                            target.writeTime = System.currentTimeMillis()
                        } else {
                            logService.log("target.writeTime >>> ${target.writeTime}")
                            target.modifyTime = System.currentTimeMillis()
                            postManager.save(target)
                            target = Gson().fromJson(fullData.joinToString(""), Post::class.java) ?: Post()
                            target.originId = target.id
                            target.id = null
                        }
                        var postMono = postManager.save(target)
                        if (postMono != null) {
                            lResultMsg = "save post"
                            lResultCode = 0
                        } else {
                            lResultMsg = "not founding user[can't find same id,email.. ]"
                            lResultCode = 7100
                        }
                    } catch (e: Exception) {
                        e.printStackTrace()
                        lResultMsg = "unknown exception"
                        lResultCode = 7999
                    }
                }
            }
        }
        val responce = ResponseEntity.ok().headers {
        }.contentType(MediaType.APPLICATION_JSON).body(ResponceResult().apply {
            this.resultCode = lResultCode
            this.resultMsg = lResultMsg
        })
        return responce
    }


    @GetMapping("rankOfViews.bjx")
    fun rankOfViews(httpServletRequest: HttpServletRequest): Mono<ResponseEntity<PostsResult>> {
        logService.log(httpServletRequest.requestURI)
        val resultCode = 0
        val resultMsg = "Success"

        return postManager.getTop10Posts()
            .collectList()  // Flux<Post> -> Mono<List<Post>>
            .map { postsList ->
                val postsResult = PostsResult().apply {
                    this.resultCode = resultCode
                    this.resultMsg = resultMsg
                    this.posts = postsList // List<Post> 할당 가능
                }
                ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(postsResult)
            }
    }

    @GetMapping("recentOfPost.bjx")
    fun recentOfPost(httpServletRequest: HttpServletRequest): Mono<ResponseEntity<PostsResult>> {
        logService.log(httpServletRequest.requestURI)
        val resultCode = 0
        val resultMsg = "Success"

        return postManager.getRecent10Posts()
            .collectList()  // Flux<Post> -> Mono<List<Post>>
            .map { postsList ->
                val postsResult = PostsResult().apply {
                    this.resultCode = resultCode
                    this.resultMsg = resultMsg
                    this.posts = postsList // List<Post> 할당 가능
                }
                ResponseEntity.ok()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(postsResult)
            }
    }

    @GetMapping("viewer/{postId}")
    fun viewer(@PathVariable postId : String) : ResultMV{
        val vm = ResultMV("content/blog/viewer")
        postManager.getPost(postId).block().apply {
            this?.title = URLDecoder.decode(this?.title)
            this?.content = URLDecoder.decode(this?.content)



            globalEvv.gapiKey?.let {
                if (this?.firstAddress?.length ?: 0 < 4){
                    try {
                        var addrs = GeocodingApi.reverseGeocode(GeoApiContext.Builder().apiKey(it).build(), LatLng(this?.firstPostLat!!,this?.firstPostLon!!)).await()
                        this.firstAddress = addrs.first().formattedAddress
                        postManager.save(this)
                    } catch (e: Exception) {}
                }
                if (this?.modifyAddress?.length ?: 0 < 4){
                    try {
                        var addrs = GeocodingApi.reverseGeocode(GeoApiContext.Builder().apiKey(it).build(), LatLng(this?.modifyLat!!,this?.modifyLon!!)).await()
                        this.modifyAddress = addrs.first().formattedAddress
                        postManager.save(this)
                    } catch (e: Exception) {}
                }
            }
            vm.modelMap.put("srcPost",this)
        }
        return vm
    }


    @GetMapping("modify.bs")
    fun modify(httpServletRequest: HttpServletRequest, @RequestParam("token") token : String?) : ResultMV{
        logService.log("incoming modify")
        val vm = ResultMV("content/blog/modify")
        val authentication = SecurityContextHolder.getContext().authentication
        val principal = authentication.principal
        if (principal is UserDetails) {
            val username = principal.username
            // 추가 정보 사용 가능
            postManager.find20()?.apply {
                forEach {
                    it.title = URLDecoder.decode(it.title)
                    val content = URLDecoder.decode(it.content)
                    it.content = if (content.length > 50) content.substring(0,150) else content
                }
                vm.modelMap.put("chunkedPosts", this.chunked(3))
            }
            vm.modelMap.put(WRITE_PERMISSION_KEY,"OK")
            vm.modelMap.put("path","editor/")
            vm.modelMap.put("SK",token)
        }
        vm.modelMap.put("rowKey","chunkedPosts_")
        return vm
    }

    @GetMapping("editor/{postId}")
    fun editor(@PathVariable postId : String) : ResultMV{
        val vm = ResultMV("content/blog/editor")
        postManager.getPost(postId).block().apply {
            this?.title = URLDecoder.decode(this?.title)
            this?.content = URLDecoder.decode(this?.content)
            vm.modelMap.put("srcPost",this)
        }
        return vm
    }


    @GetMapping("posts")
    fun posts(pageable: Pageable)  : ResultMV{
        val vm = ResultMV("content/blog/posts")
        try {
            vm.modelMap.put("Posts", postManager.find20(pageable).apply {
                this.forEach {
                    println("it.id ==> ${it.id}")
                    it.title = URLDecoder.decode(it.title)
                    it.content = URLDecoder.decode(it.content)
                    val parser: Parser = Parser.builder().build()
                    val document: Node = parser.parse(it.content)
                    val renderer = HtmlRenderer.builder().build()
                    Jsoup.parse(renderer.render(document))?.let { doc ->
                        val firstImg: Element? = doc.select("img")?.first()
                        val imgSrc: String = firstImg?.attr("src") ?: ""
                        it.image = imgSrc
                        it.thumb = imgSrc.replace(imgSrc.split("/").last(), imgSrc.split("/").last().replace(".","_thumbnail."))
                        generateThumbnail(imgSrc.split("/").last(), 200)
                        it.html = doc.text()
                    }
                    it.title = if ((it.title?.length ?: 0) >= 1) it.title else ""
                }
            })
        }catch (ex: Exception){ex.printStackTrace()}

        return vm
    }

    fun generateThumbnail(originalPath: String, targetWidth: Int) {
        try {
            val originalFile = File("$uploadPath${File.separator}$originalPath")
            println("origin ${originalPath}")
            println("thumb ${originalPath
                .replace(".", "_thumbnail.")}")
            // 썸네일 경로 생성 (예: /upload/uuid.jpg → /upload/uuid_thumbnail.jpg)
            val thumbnailPath = originalPath
                .replace(".", "_thumbnail.")


            val thumbnailFile = File("$uploadPath${File.separator}$thumbnailPath")
            // 썸네일 이미 존재하면 종료
            if (thumbnailFile.exists()) {
                println("썸네일 이미 존재: $thumbnailPath")
                return
            }


            // 원본 파일 존재 확인
            if (!originalFile.exists()) {
                println("원본 파일 없음: $originalPath")
                return
            }

            // 썸네일 생성 (가로 기준 비율 유지)
            Thumbnails.of(originalFile)
                .width(targetWidth)
                .keepAspectRatio(true)
                .toFile(thumbnailFile)

            println("썸네일 생성 완료: $thumbnailPath")
        } catch (e: IOException) {
            println("썸네일 생성 실패: ${e.message}")
        }
    }


    @GetMapping("recent")
    fun recent() : ResultMV{
        val vm = ResultMV("content/blog/viewer")
        locationLogService.find10().forEach {
            logService.log(Gson().toJson(it))
        }
        locationLogService.getLocationLog()?.let {
            try {
                val client0 = WebClient.create()
                val result = client0.get()
                    .uri("http://api.weatherapi.com/v1/current.json?key=${globalEvv.weatherApiKey}&q=${it.mLatitude},${it.mLongitude}&aqi=no")
                    .retrieve()
                    .bodyToMono(String::class.java)
                    .block() ?: "FAIL"
                Gson().fromJson(result, CurrentWeather::class.java)?.let { sss ->
                    logService.log("지역:${sss.location?.name}\n날씨:${sss.current?.condition?.text}\n온도:${sss.current?.temp_c}\n습도:${sss.current?.humidity}\n" +
                            "체감온도:${sss.current?.feelslike_c}\nhttps://www.accuweather.com/ko/search-locations?query=${it.mLatitude},${it.mLongitude}")
                }
            }
            catch (e : Exception) {

            }
        }
        return vm
    }


    @Value("\${image.upload.path}")
    private val uploadPath: String? = null

    @Value("\${resource.handler}")
    private val resourceHandler: String? = null


    @ResponseBody
    @GetMapping("post/images/{fileName}")
    fun getImage(@PathVariable fileName : String) : Resource {
        val imgUploadPath = ("file:" +uploadPath + File.separator + fileName)
        return UrlResource.from(imgUploadPath)
    }

    @PostMapping("post/imageUpload.bjx")
    fun postImage(@RequestPart("file") upload: MultipartFile, res: HttpServletResponse, req: HttpServletRequest): ResponseEntity<FileSaveResult> {
        var lResultCode = 0
        var lResultMsg = "Success"
        var out: FileOutputStream? = null
        var targetFile: File? = null

        val uuid = UUID.randomUUID()
        val extension: String = getFileExtension(upload.originalFilename) ?: ""

        try {
            val bytes = upload.bytes

            val f = File(uploadPath)
            if (!f.exists()) f.mkdirs()

            // 원본 이미지 저장 경로
            val originalImagePath = "$uploadPath${File.separator}$uuid.$extension"
            logService.log("Original image path: $originalImagePath")

            // 썸네일 저장 경로
            val thumbnailPath = "$uploadPath${File.separator}${uuid}_thumbnail.$extension"
            logService.log("Thumbnail path: $thumbnailPath")

            targetFile = File(originalImagePath)
            if (!targetFile.parentFile.exists()) targetFile.parentFile.mkdirs()

            // 원본 이미지 저장
            out = FileOutputStream(originalImagePath)
            out.write(bytes)
            out.flush()

            // 썸네일 생성 및 저장
            Thumbnails.of(originalImagePath)
                .width(200) // 가로 크기를 설정
                .keepAspectRatio(true)
                .toFile(thumbnailPath)

            logService.log("Original image saved: ${File(originalImagePath).exists()}")
            logService.log("Thumbnail saved: ${File(thumbnailPath).exists()}")

            // 메타데이터 읽기 (원본 이미지에서)
            val metadata: Metadata? = ImageMetadataReader.readMetadata(File(originalImagePath))
            metadata?.let {
                it.directories?.forEach { directory ->
                    logService.log(directory.name)
                    logService.log(directory.tags.map { tag ->
                        logService.log("tag.tagName >>> ${tag.tagName} || tag.description  ${tag.description}")
                    }.joinToString(" \n"))
                }
            }

        } catch (e: IOException) {
            e.printStackTrace()
            lResultCode = 1
            lResultMsg = "Error: ${e.message}"
        } finally {
            try {
                out?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

        return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(FileSaveResult().apply {
            this.resultCode = lResultCode
            this.resultMsg = lResultMsg
            this.fileName = "$uuid.$extension"
            this.thumbnailName = "${uuid}_thumbnail.$extension"
        })
    }

}