package kr.lunaticbum.back.lun.model

import com.google.gson.Gson
import kr.lunaticbum.back.lun.configs.GlobalEnvironment
import kr.lunaticbum.back.lun.utils.LogService
import lombok.AllArgsConstructor
import lombok.Data
import lombok.NoArgsConstructor
import org.bson.codecs.pojo.annotations.BsonIgnore
import org.jsoup.Jsoup
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.annotation.Id
import org.springframework.data.domain.Sort
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.Query
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import org.springframework.stereotype.Service
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*

class BumsPrivate {
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "LocationLog")
class LocationLog  {
    var mFeatureName: String? = null
    var mAddressLines: ArrayList<String> = arrayListOf()
    var mAdminArea: String? = null
    var mSubAdminArea: String? = null
    var mLocality: String? = null
    var mSubLocality: String? = null
    var mThoroughfare: String? = null
    var mSubThoroughfare: String? = null
    var mPremises: String? = null
    var mPostalCode: String? = null
    var mCountryCode: String? = null
    var mCountryName: String? = null
    var mLatitude = 0.0
    var mLongitude = 0.0
    var mPhone: String? = null
    var timeString : String? = null
    var mUrl: String? = null
    var time : Long = 0L
    var userId : String? = null

    var bettween : String? = null

    override fun toString(): String {
        val buffer = StringBuffer()
        buffer.append(mFeatureName).append("|").append("\n")
        buffer.append(mAddressLines.joinToString(" , ")).append("|").append("\n")
        buffer.append(mAdminArea).append("|").append("\n")
        buffer.append(mSubAdminArea).append("|").append("\n")
        buffer.append(mLocality).append("|").append("\n")
        buffer.append(mSubLocality).append("|").append("\n")
        buffer.append(mThoroughfare).append("|").append("\n")
        buffer.append(mSubThoroughfare).append("|").append("\n")
        buffer.append(mPremises).append("|").append("\n")
        buffer.append(mPostalCode).append("|").append("\n")
        buffer.append(mCountryCode).append("|").append("\n")
        buffer.append(mCountryName).append("|").append("\n")
        buffer.append(mLatitude).append("|").append("\n")
        buffer.append(mLongitude).append("|").append("\n")
        buffer.append(mPhone).append("|").append("\n")
        buffer.append(mUrl).append("|").append("\n")
        return buffer.toString()
    }
}

@Repository
interface LocationLogRepository : ReactiveMongoRepository<LocationLog, String> {
    @Query("{ 'time' : { \$gte: ?0 } }")
    fun findRecent(since: Long, sort: Sort): Flux<LocationLog>

//    @Query("SELECT l FROM LocationLog l WHERE l.timeString >= :since ORDER BY l.timeString DESC")
//    fun findRecent(@Param("since") since: String): Flux<LocationLog>

    fun findTop30ByOrderByTimeDesc(): Flux<LocationLog>
    fun findAllBy() : Mono<LocationLog>
    fun findFirstByOrderByTimeDesc() : Mono<LocationLog>
    fun findFirstByUserIdOrderByTimeDesc(userId: String) : Mono<LocationLog>
    fun save(log:  LocationLog): Mono<LocationLog>
}
interface LocationService {

}

@Service
class LocationLogService : LocationService {
    @Autowired
    private lateinit var logService: LogService

    @Autowired
    private lateinit var logRepository: LocationLogRepository

    fun find10() : List<LocationLog> {
        val sinceMills = System.currentTimeMillis() - ((24 * 60 * 60 * 1000) * 7)
        println("sinceMills >> $sinceMills")
        val sort = Sort.by(Sort.Direction.DESC, "time")  // 오름차순 정렬
//        val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
//        val since = LocalDateTime.now().minusHours(24).format(formatter)
//        println("since >> $since")
        val flux = filterByDistanceReactive(logRepository.findRecent(sinceMills,sort), 10.0)
        return flux.collectList().block(Duration.ofSeconds(30)) ?: listOf()
    }

    fun getLocationLog() : LocationLog? {
        return logRepository.findFirstByOrderByTimeDesc().block()
    }

    fun getLocationLogBy(userId : String) : LocationLog? {
        return logRepository.findFirstByOrderByTimeDesc().block()
    }
    fun filterByDistanceReactive(flux: Flux<LocationLog>, minDistanceMeter: Double): Flux<LocationLog> {
        return flux
            .buffer(2, 1)
            .filter { pair ->
                if (pair.size < 2) true
                else haversine(pair[0].mLatitude, pair[0].mLongitude, pair[1].mLatitude, pair[1].mLongitude) >= minDistanceMeter
            }
            .map { pair ->
                val distance = if (pair.size < 2) 0.0 else haversine(pair[0].mLatitude, pair[0].mLongitude, pair[1].mLatitude, pair[1].mLongitude)
                val base = pair[0]
                println("base >>> ${base.time} ${base.timeString}")
                base.bettween = String.format("%.2f m", distance) // 소수점 두자리까지 거리 표시
                base
            }
    }

    // Haversine 거리계산 함수 (단위:m)
    fun haversine(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
        val R = 6371000.0 // 지구 반지름(m)
        val dLat = Math.toRadians(lat2 - lat1)
        val dLon = Math.toRadians(lon2 - lon1)
        val a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
                Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                Math.sin(dLon / 2) * Math.sin(dLon / 2)
        val c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
        return R * c
    }

    fun save(log: LocationLog) {
        println("saved msg before ${log}")
        logRepository.save(log).subscribe( {  println("saved msg after ${it}") },{e -> e.printStackTrace()},{
            println("saved msg  comp")
        })
    }
}

interface RssDataInterface {
    fun title() : String
    fun thumbnailUrl() : String
    fun originPage() : String
    fun description() : String
    fun pubDate() : Long
    fun category() : RssDataType
    fun getCho() : String?

}
enum class RssDataType {
    NO_DATA,
    YOUTUBE,
    NewsFeed,
    GURU,
    Most,
    TAGS,
    REDDIT,
    REDDIT_nsfw,
    Dotax,
    FmKorae,
    DcInside,
    RuliWeb,
    Clien,
    TheQoo,
    Arca;

//    fun getResId() = when (this) {
//        YOUTUBE -> R.drawable.youtube
//        REDDIT, REDDIT_nsfw -> R.drawable.reddit
//        Dotax -> R.drawable.daum
//        FmKorae -> R.drawable.fmk
//        DcInside -> R.drawable.dcinside
//        Arca -> R.drawable.arca
//        else -> {
//            0
//        }
//    }

    fun defaultImgSize() = when (this) {
        YOUTUBE -> 200
        REDDIT_nsfw,GURU,Most -> 360
        else -> { 120 }
    }

//    fun getDefaultVisibiliy() = when (this) {
//        REDDIT_nsfw,GURU,Most,NewsFeed -> View.GONE
//        else -> { View.VISIBLE }
//    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "RssData")
class RssData : RssDataInterface {

    @Id
    var originPage : String? = null
    var title : String? = null
    var description : String? = null
    var thumbnail : String? = null
    var pubDate : Long = 0L
    var category : String? = null

    var chosung : String? = null


    @BsonIgnore
    var mRssDataType : RssDataType? = null
    override fun title(): String {
        return  when(category()){
            RssDataType.NewsFeed -> {
                if(title?.length ?: 0 > 30) title?.substring(0,30).plus("...") else title ?: ""
            }
            else -> title ?: ""
        }.apply {
//            chosung = JamoUtils.split(this).joinToString("")
        }
    }

    override fun thumbnailUrl(): String {
        return thumbnail ?: ""
    }

    override fun originPage(): String {
        return originPage ?: ""
    }

    override fun description(): String {

        return  when(category()){
            RssDataType.YOUTUBE -> {
                if(description?.contains("게시자") == true) description!!.split("게시자")[0] else description ?: ""
            }
            RssDataType.NewsFeed -> {
                category().name
            }
            else ->  description.plus(" / ").plus(category().name)
        }
    }

    override fun pubDate(): Long {
        return pubDate
    }

    override fun category(): RssDataType {
        if (mRssDataType == null)
            mRssDataType = RssDataType.valueOf(category!!)
        return mRssDataType!!
    }

    override fun getCho(): String? {
        return chosung
    }
}


val USAGT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
fun String.getJ() = Jsoup.connect(this).userAgent(USAGT).get()
object FeedParseManager {
    val parsers = listOf<SoInterface>(QVZTb2dpcmw,SkFWTW9zdA)
    fun parse(doc : org.jsoup.nodes.Document, service: RssDataService) {
        try {
            parsers.filter { doc.title().contains(it.getName()) }.first()?.let {
                it.parse(doc,service)
            }
        } catch (e : Exception) {

            e.printStackTrace()
        }
    }
}
interface SoInterface{
    fun getName() : String
    fun parse(doc : org.jsoup.nodes.Document,service: RssDataService)
}
object QVZTb2dpcmw : SoInterface {
    override fun getName(): String {
        return String(Base64.getMimeDecoder().decode(this.javaClass.simpleName.plus("==").toByteArray()))
    }
    override fun parse(doc : org.jsoup.nodes.Document, service : RssDataService) {
        var lists = arrayListOf<RssData>()
        doc.getElementsByTag("article").forEach { article ->

            val title = article.getElementsByTag("a").get(0).attr("title")
            val href = article.getElementsByTag("a").get(0).attr("href")
            val img = article.getElementsByTag("img").get(0).attr("data-src")
            service.save(RssData().apply {
                this.originPage = href
                this.title = title
                this.description = "Sogirl"
                this.thumbnail = img
                this.pubDate = Date().time
                this.category = RssDataType.GURU.name

            }) {
//                CoroutineScope(Dispatchers.IO).launch {
//                    service.sendMsg("${title}\n${img}\n${href}")
//                }
            }
        }
//        lists.map {
//            service.sendMsg("${it.title}\n${it.description}\n${it.thumbnail}\n${it.originPage}")
//        }
    }
}

object SkFWTW9zdA : SoInterface {
    var dmy = SimpleDateFormat("dd-MM-yyyy")
    override fun getName(): String {
        return String(Base64.getMimeDecoder().decode(this.javaClass.simpleName.plus("==").toByteArray()))
    }
    override fun parse(doc: org.jsoup.nodes.Document, service: RssDataService) {
        var lists = arrayListOf<RssData>()
        doc.getElementsByClass("card").forEach { card ->
            var thumb = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("src") else ""
            if (thumb.contains("No+Poster")) thumb = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("data-src") else thumb
            var model = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("alt") else ""
            if(card.getElementsByClass("card-block").size > 0) if(card.getElementsByClass("card-block").size > 0) {
                val link = card.getElementsByClass("card-block").get(0).getElementsByTag("a").get(0).attr("href")
                val title = card.getElementsByClass("card-block").get(0).getElementsByTag("a").get(0).attr("title")
                val date  = card.getElementsByTag("span").get(0).text()
                service.save(RssData().apply {
                    lists.add(this)
                    description = model
                    thumbnail = thumb
                    originPage = link
                    this.title = title
                    category = RssDataType.Most.name
                    try {
                        pubDate = dmy.parse(date).time
                    }catch (e : Exception) {e.printStackTrace()}
                }){
//                    CoroutineScope(Dispatchers.IO).launch {
//                        service.sendMsg("${title}\n${thumb}\n${link}")
//                    }
                }
            }
        }
//        service.sendMsg(lists.map {
//            "${it.title}\n${it.description}\n${it.thumbnail}\n${it.originPage}\n"
//        }.joinToString(" \n "))
    }
}
@Repository
interface RssDataRepository : ReactiveMongoRepository<RssData, String> {
    fun findFirstByOriginPageEquals(originPage : String): Mono<RssData>
    fun findAllByOrderByPubDate() : Mono<List<RssData>>
    fun save(log:  RssData): Mono<RssData>
}

@Service
class RssDataService {
    @Autowired
    private lateinit var logService: LogService

    @Autowired
    private lateinit var rssDataRepository: RssDataRepository
    fun hasItem(originPage : String) {

    }
    fun getLocationLog() : List<RssData>? {
        return rssDataRepository.findAllByOrderByPubDate().block()
    }


    fun save(log: RssData, callback : (Boolean)->Unit) {
        println("saved msg before ${Gson().toJson(log)}")
        log.originPage?.let {
            if(rssDataRepository.findFirstByOriginPageEquals(it).block() == null) {
                rssDataRepository.save(log)
                    .subscribe({ println("saved msg after ${it}") }, { e -> e.printStackTrace() }, {
                        println("saved msg  comp")
                        callback(true)
                    })
            } else {
                println("있어???")
            }
        }
    }

    @Autowired
    lateinit var globalEvv : GlobalEnvironment

    suspend fun sendMsg(data : String) {
        val client = WebClient.create()
        client.get()
            .uri("https://api.telegram.org/${globalEvv.telegramBotKey}/sendMessage?chat_id=${globalEvv.telegramMyId}&text=${data}")
            .retrieve()
            .bodyToMono(String::class.java).block() ?: "FAIL"
    }
}