package kr.lunaticbum.back.lun.service



import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import io.micrometer.observation.ObservationRegistry
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
import kr.lunaticbum.back.lun.controllers.TelegramSendMsg
import kr.lunaticbum.back.lun.model.*
import org.jsoup.Jsoup
import org.jsoup.select.Elements
import org.springframework.ai.embedding.EmbeddingRequest
import org.springframework.ai.ollama.OllamaEmbeddingModel
import org.springframework.ai.ollama.api.OllamaApi
import org.springframework.ai.ollama.api.OllamaOptions
import org.springframework.ai.ollama.management.ModelManagementOptions
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.http.MediaType
import org.springframework.scheduling.annotation.Async
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import reactor.kotlin.core.publisher.toMono
import java.text.SimpleDateFormat
import java.time.Duration
import java.util.*
import kotlin.collections.ArrayList


@Service
class Lama {




    //, val date : String = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
//    data class QSearchData(val query : FloatArray,val limit : Int)

    data class QSearchData(val vector : FloatArray,val limit : Int)
    data class QPut(val points : ArrayList<QData>)
    data class QData(val id : Long, val vector : FloatArray, val payload : SearXngResult)

    data class QContentsList(var ids : ArrayList<Int> = ArrayList(), var with_payload : Boolean = true, var with_vector : Boolean = false)
    //    fun makeCollection() : String{
//
//        class CollectionPut {
//            val name = "blama_vectors"
//            val vector_size = 3072
//            val distance = "Cosine"
//        }
//        val qUrl = "https://ollama.lunaticbum.kr/collections"
//        val client = WebClient.create()
//        return client.post()
//            .uri(qUrl)
//            .header("api-key","blama-admin-key-gb")
//            .body(BodyInserters.fromValue(Gson().toJson(CollectionPut())))
//            .retrieve()
//            .bodyToMono(String::class.java).timeout(Duration.ofMinutes(20L)).block() ?: ""
//    }

    private fun checkCollection() : Long {
        val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors"
        val client = WebClient.create()
        return client.get()
            .uri(qUrl)
            .header("api-key", "blama-admin-key-gb")
            .retrieve()
            .bodyToMono(QCollection::class.java).timeout(Duration.ofMinutes(20L)).block()?.result?.points_count ?: 0L
    }

    fun jsopFilter(url : String) : String {
        val joinString = "\n#"
        var lastElement : Elements = Elements()
        var body = Jsoup.connect(url).timeout(30000).get().body()
        var elements : Elements? = null
        if (url.contains("nate.com", true)) {
            if (url.contains("view", true)) {
                elements = body.select("[class*=articleView]")
            }else {
                elements = body.select("[class*=postRankSubjectList]")
            }
        } else if (url.contains("newsis.com/view", true)) {
            elements = body.select("[class*=articleView]")
        } else if (url.contains("blog.naver.com", true)) {
            elements = body.select("[class*=se-viewer]")
        } else if (url.contains("bbc.com/korean/articles", true)) {
            elements = body.select("main[role$=main]")
        } else if (url.contains("chosun.com/client", true)) {
            elements = body.select("[class*=articleBody]")
        } else if (url.contains("nocutnews.co.kr/news", true)) {
            elements = body.select("[class*=container]")
        } else if (url.contains("hani.co.kr/arti/", true)) {
            elements = body.select("[class*=ArticleDetail]")
        } else if (url.contains("yna.co.kr/view", true)) {
            elements = body.select("[class*=container]")
        } else if (url.contains("newspim.com/news", true)) {
            elements = body.select("[class*=container]")
        } else {

        }
        if (elements?.size ?: 0 > 0) {
            elements?.forEach {
                lastElement.add(it)
            }
        }

        if (lastElement.size < 1) {
            arrayOf("container","article","main","viewer","content").forEach {
                var result = Elements()
                result.addAll(body.select("[class*=$it]"))
                result.addAll(body.select("[id*=$it]"))
                result.addAll(body.select(it))
                result.forEach { if (it.text().length > 100 && it.children().size < 5) { lastElement.add(it) } }
            }
        }
        return if (lastElement.size > 0) {
            lastElement.eachText().joinToString(joinString)
        } else {
            body.children().eachText().joinToString(joinString)
        }
    }

//    class WebScrap {
//        @SerializedName("query", alternate = ["question"])
//        var query: String? = null
//        var original_html: String? = null
//        var original_content: String? = null
//        var summary: String? = null
//        var keywords: ArrayList<String>? = null
//        var related_links: ArrayList<String>? = null
//        var relatedness_score: Double = 0.0
//    }



    fun addDocuments(query : String , refinedQuery: RefinedQuery?) {
        var querys : ArrayList<String> = ArrayList()
        querys.add(query)
        refinedQuery?.ko_query?.let { querys.add(it) }
        refinedQuery?.en_query?.let { querys.add(it) }
        refinedQuery?.keywords?.let { querys.add(it.joinToString { " " })}
        val readedUrls = ArrayList<String>()
        querys.forEach { refinedQuery ->
            val gSearch = "https://psn.lunaticbum.kr/search?q=${refinedQuery?.replace("오늘", SimpleDateFormat("yyyMMdd").format(Date()))}&language=ko&time_range=month&safesearch=0&categories=general&format=json"
            println("gSearch >>> ${gSearch}")
            WebClient.create().get()
                .uri(gSearch)
                .retrieve()
                .bodyToMono(SearXng::class.java).timeout(Duration.ofMinutes(20L)).block()?.let { gsResult ->
                    gsResult.results?.filter { it.url?.startsWith("https://") == true && it.score > 0.4}?.forEach {
                        println("in filter ${it.url}")
                        if (readedUrls.contains(it.url) == false) {
                            readedUrls.add(it.url!!)
                            it.originQuery = query
                            it.refinedQuery = refinedQuery
                            println(it.title)
                            try {
                                jsopFilter(it.url!!).let { text ->
                                    it.originHtml = text
                                    webPageSummarize(it,text)
                                }
                            }catch(e:Exception){e.printStackTrace()}
                        }
                    }
                }
            println("end of search")
        }
    }

    var format = "원문:\n'%s'\n원문의 웹 페이지 소스는 '%s'이 질문에 대해 연관 결과로 받은 내용이야. 해당 정보를 파악해서 'query:{질문},contents:{본문내용 한국어},summary:{요약 한국어},keywords:[키워드],related_links:[링크],relatedness_score:{0.0~10.0}'이 형식의 결과만들어줘"
    internal fun makeSummarizeRequestMsg(it : SearXngResult) : String= format.format(it.originHtml,it.originQuery)

    internal fun makeCahtReq(reqMsg:String) = OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages(listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content(reqMsg).build())).build()

    @Async
    fun webPageSummarize(it : SearXngResult , text : String) {
        try {
            val chatClient = OllamaApi("https://lama.lunaticbum.kr")
            val embeddingModel = OllamaEmbeddingModel(
                chatClient, OllamaOptions.builder().build(), ObservationRegistry.create(), ModelManagementOptions.defaults())
            println("text >>>>> ${text?.chunked(50)?.first() ?: ""}")
            var dispoable = chatClient.chat(makeCahtReq(makeSummarizeRequestMsg(it))).toMono().subscribe({aiResponce ->
                it.pageData = aiResponce.message.content
                println("summary result >>>>>  ${it.pageData}")
                val embeddingResponse = embeddingModel.call(
                    EmbeddingRequest(
                        listOf(aiResponce.message.content),
                        OllamaOptions.builder()
                            .model("bge-m3")
                            .truncate(false).build()
                    )
                )
                infomationDic.put(it.url!!,aiResponce.message.content)
                val sdss = QPut(arrayListOf())
                sdss.points.add(QData(id = System.currentTimeMillis(),embeddingResponse.result.output,it))
                if (sdss.points.size > 0) {
                    val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points")
                    val client = WebClient.create()
                    client.put()
                        .uri(qUrl)
                        .header("api-key", "blama-admin-key-gb")
                        .body(BodyInserters.fromValue(Gson().toJson(sdss)))
                        .retrieve()
                        .bodyToMono(String::class.java).timeout(Duration.ofMinutes(20L)).subscribe(
                            {resultString -> },{error-> error.printStackTrace()}
                        )
                }
            },{err->
                err.printStackTrace()
            })
        }catch (e : Exception) {

            e.printStackTrace()
        }
    }

    class RefinedQuery {
        var ko_query : String? = null
        var en_query : String? = null
        var keywords : Array<String>? = null
    }

    var queryFormat = "질문:\n'%s'\n앞은 질문의 내용을 정리해서 '{ko_query:한국어 질문,en_query:영어 번역된 질문,ko_keywords:[한국어 키워드],en_keyword:[영문키워드]}'이 형식의 결과를 부탁할께"
    internal fun makeQuerySummarizeRequestMsg(query : String) : String= queryFormat.format(query)
    fun querySummarize(query: String) : RefinedQuery? {
        var refinedQuery : RefinedQuery? = null
        try {
            val chatClient = OllamaApi("https://lama.lunaticbum.kr")
            var dispoable = chatClient.chat(makeCahtReq(makeQuerySummarizeRequestMsg(query))).toMono().subscribe({aiResponce ->
                println("summary result >>>>>  ${aiResponce.message.content}")
                refinedQuery =  Gson().fromJson(aiResponce.message.content, RefinedQuery::class.java)
            },{err->
                err.printStackTrace()
            })
        }catch (e : Exception) {

            e.printStackTrace()
        }
        return refinedQuery
    }


    @Async
    public fun embedQuery(embedFlots : FloatArray) : QContents?{
        val qUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points/search")
        val client = WebClient.create()
        var lists = client.post()
            .uri(qUrl)
            .header("api-key","blama-admin-key-gb")
            .body(BodyInserters.fromValue(Gson().toJson(QSearchData(embedFlots,3))))
            .retrieve()
            .bodyToMono(QSearch::class.java).timeout(Duration.ofMinutes(20L)).block()
        println(Gson().toJson(lists))
        return if (lists?.result?.size ?: 0 > 0) {
            val qContents = QContentsList()
            lists?.result?.forEach {

                qContents.ids.add(it.id)
            }
            val qCUrl = "https://ollama.lunaticbum.kr/collections/blama_vectors".plus("/points")
            val client2 = WebClient.create()
            client2.post()
                .uri(qCUrl)
                .header("api-key", "blama-admin-key-gb")
                .body(BodyInserters.fromValue(Gson().toJson(qContents)))
                .retrieve()
                .bodyToMono(QContents::class.java).timeout(Duration.ofMinutes(20L)).block()
        } else {
            null
        }
    }

    @Autowired
    lateinit var globalEvv : GlobalEnvironment

    var infomationDic = hashMapOf<String,String>()
    suspend fun generateResponse(query: String?, targetId: String? = globalEvv.telegramMyId) {
        infomationDic.clear()
        val chatClient = OllamaApi("https://lama.lunaticbum.kr")
        val embeddingModel = OllamaEmbeddingModel(
            chatClient, OllamaOptions.builder().build(), ObservationRegistry.create(), ModelManagementOptions.defaults())
        println("On generateResponse :: find something ${query}")
        query?.let { originalQuery ->
            var embeddingResponse = embeddingModel.call(EmbeddingRequest(listOf(originalQuery), OllamaOptions.builder().model("bge-m3").truncate(false).build()))
            addDocuments(originalQuery, querySummarize(originalQuery))
            println("points size  ${embeddingResponse.result.output.size}")
            var context : String? = ""
            try {
                embedQuery(embeddingResponse.result.output)?.result?.forEach { result ->
                    if (infomationDic.contains(result.payload?.url ?: "NONE") == false) {
                        context += "\n# :".plus(if (result.payload?.pageData?.length ?: 0 > 10) {
                            result.payload?.pageData
                        } else {
                            result.payload?.content
                        })
                    }
                }
            }catch (e:Exception){
                e.printStackTrace()
            }

            infomationDic.iterator().forEach { context += "\n#${it.key}:${it.value}" }

            val prompt = "참조:\n$context\n참조 내용을 고려해서\n해당 질문:${query}\n에 {질문내용:[한국어],답변내용:[한국어],전체키워드:[],참조링크:[]}형식으로 대답 해줘 ".trimIndent()
            println(prompt)
            val response: OllamaApi.ChatResponse = chatClient.chat(OllamaApi.ChatRequest.Builder("phi4:14b").stream(false).format("json").messages(
                listOf(OllamaApi.Message.Builder(OllamaApi.Message.Role.USER).content(prompt).build())
            ).build())

            println(response.message.content)
            CoroutineScope(Dispatchers.IO).launch {
                var toalmsg = "${query}의 대답이 도착했어요.\n${response.message.content}"
                val fullUrl = "https://api.telegram.org/${globalEvv.telegramBotKey}/sendMessage"
                toalmsg.chunked(512).forEach { chunkedMsg ->
                    println("fullUrl >>> ${fullUrl}")
                    (targetId ?: globalEvv.telegramMyId)?.let {
                        var tlgSend = TelegramSendMsg(it, chunkedMsg)
                        WebClient
                            .create(fullUrl)
                            .post()
                            .contentType(MediaType.APPLICATION_JSON)
                            .body(BodyInserters.fromValue(Gson().toJson(tlgSend)))
                            .retrieve().bodyToMono(String::class.java).timeout(Duration.ofMinutes(20L))
                            .block()?.let { result ->
                                println("result >>> ${result}")
                            }
                    }
                }
            }
            println("On generateResponse :: END OF Answer")
        }
    }


}