package com.lanyuanxiaoyao.digtal.market.runner

import cn.hutool.core.date.DateUtil
import cn.hutool.core.util.NumberUtil
import cn.hutool.crypto.SecureUtil
import com.lanyuanxiaoyao.digtal.market.Article
import com.lanyuanxiaoyao.digtal.market.ArticleRepository
import com.lanyuanxiaoyao.digtal.market.keywords
import com.lanyuanxiaoyao.digtal.market.service.DescriptionService
import com.lanyuanxiaoyao.digtal.market.sites
import com.lanyuanxiaoyao.squirrel.core.common.Management
import com.lanyuanxiaoyao.squirrel.core.common.PageParseException
import dev.failsafe.Failsafe
import dev.failsafe.FailsafeException
import dev.failsafe.RetryPolicy
import dev.failsafe.function.CheckedSupplier
import jakarta.annotation.Resource
import java.util.concurrent.atomic.AtomicLong
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import kotlinx.coroutines.runBlocking
import org.jsoup.Jsoup
import org.slf4j.LoggerFactory
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service

@Service
class NewsRunner : Runner {
    private val logger = LoggerFactory.getLogger(javaClass)
    private val retryPolicy = RetryPolicy
        .builder<Any>()
        .withDelay(10.seconds.toJavaDuration())
        .withMaxRetries(2)
        .handleIf { e -> (e is PageParseException).not() }
        .build()

    @Resource
    private lateinit var articleRepository: ArticleRepository

    @Resource
    private lateinit var descriptionService: DescriptionService

    @Resource
    private lateinit var management: Management

    @Scheduled(cron = "0 0 6-18 * * ?")
    override fun run() {
        sites.forEach { site ->
            logger.info("站点: {}", site.name)
            keywords.forEach { keyword ->
                val url = site.search.replace("{query}", keyword)
                logger.info("类目: {}, 地址: {}", keyword, url)
                val hashList = articleRepository.findAllId()
                val links = parseArticleLink(site.code, url, false)
                val total = links.size
                val current = AtomicLong(0)
                links
                    .filter {
                        if (hashList.contains(it.hash)) {
                            current.addAndGet(1)
                            false
                        } else true
                    }
                    .forEach { link ->
                        Thread.sleep(500)
                        logger.info("进度：{} 采集文章：{} {}", NumberUtil.formatPercent(current.addAndGet(1) * 1.0 / total, 2), link.title, link.url)
                        parseArticle(site.code, link.url, link.title, link.datetime, link.hash)?.let { article ->
                            if (!article.text.isNullOrBlank()) {
                                val triple = descriptionService.parseDescription(article.text)
                                logger.info("小标题：{}", triple?.first)
                                logger.info("描述：{}", triple?.second)
                                logger.info("相关度：{}", triple?.third)
                                article.subtitle = triple?.first
                                article.description = triple?.second
                                article.score = triple?.third
                            }

                            article.category = keyword
                            articleRepository.save(article)
                        }
                    }
            }
        }
        logger.info("本轮采集完成")
    }

    fun parseArticle(code: String, url: String, title: String?, datetime: String?, hash: String): Article? {
        logger.info("Title: {} Datetime: {}", title, datetime)
        val page = try {
            Failsafe
                .with(retryPolicy)
                .get(CheckedSupplier {
                    runBlocking {
                        management.parse(code, url, properties = mutableMapOf<String, String>().apply {
                            title?.let { put("title", title) }
                            datetime?.let { put("datetime", datetime) }
                        })
                    }
                })
        } catch (e: FailsafeException) {
            logger.error("Parse failure", e)
            return null
        }
        (page["text"] as? Map<*, *>)?.let { text ->
            val createTime = try {
                DateUtil
                    .parse(
                        (text["datetime"] as String?)?.trim(),
                        "yyyy-MM-dd HH:mm:ss",
                        "yyyy-MM-dd",
                        "yyyy年MM月dd日 HH:mm",
                        "yyyy-MM-dd HH:mm",
                    )
                    .toTimestamp()
            } catch (e: Exception) {
                logger.error("Parse ${text["datetime"]} error", e)
                null
            }
            val source = text["source"]?.let {
                val document = Jsoup.parse((it as String))
                document
                    .select("script")
                    .forEach { node -> node.remove() }
                document.forEachNode { node -> node.removeAttr("style") }
                document
                    .body()
                    .html()
            }
            return Article(
                id = hash,
                code = code,
                url = url,
                title = text["title"] as String?,
                author = text["author"] as String?,
                category = null,
                text = if ((text["content"] as String?) == null) null else text["content"] as String,
                html = source,
                subtitle = null,
                description = null,
                score = null,
                createTime = createTime,
                pushed = false,
            )
        } ?: return null
    }

    data class ArticleLink(val url: String, val hash: String, val title: String?, val datetime: String?)

    fun parseArticleLink(code: String, url: String, recursive: Boolean = false): List<ArticleLink> {
        var next: String? = url
        val links = mutableListOf<ArticleLink>()
        do {
            logger.info("解析目录：{}", next)
            val page = try {
                Failsafe
                    .with(retryPolicy)
                    .get(CheckedSupplier {
                        runBlocking { management.parse(code, next!!) }
                    })
            } catch (e: FailsafeException) {
                logger.error("Parse failure", e)
                continue
            }
            @Suppress("UNCHECKED_CAST") (page["list"] as? List<Map<String, Any>>)?.let { list ->
                for (item in list) {
                    val title = item["title"] as String?
                    val datetime = item["datetime"] as String?
                    val link = item["link"] as String?
                    if (link.isNullOrBlank()) {
                        logger.warn("链接为空：{} {}", title, link)
                    } else {
                        links.add(ArticleLink(link, SecureUtil.md5(link), title, datetime))
                    }
                }
            }
            next = page["next"] as String?
        } while (recursive && !next.isNullOrBlank())
        return links
    }
}
