package com.jishisongfu.duobibi.data.repo

import com.jishisongfu.duobibi.data.api.ArenaApi
import com.jishisongfu.duobibi.data.api.ArenaQuoteRequest
import com.jishisongfu.duobibi.data.api.ClusterDto
import com.jishisongfu.duobibi.data.api.OcrApi
import com.jishisongfu.duobibi.data.api.ParseApi
import com.jishisongfu.duobibi.data.api.ParseRequest
import com.jishisongfu.duobibi.data.api.ProtectApi
import com.jishisongfu.duobibi.data.api.TrackPriceRequest
import com.jishisongfu.duobibi.data.api.WorthBuyApi
import com.jishisongfu.duobibi.data.api.WorthBuyRequest
import com.jishisongfu.duobibi.data.api.WorthBuyResponse
import com.jishisongfu.duobibi.data.db.AlertMode
import com.jishisongfu.duobibi.data.db.AlertStatus
import com.jishisongfu.duobibi.data.db.Cluster
import com.jishisongfu.duobibi.data.db.ClusterDao
import com.jishisongfu.duobibi.data.db.PriceAlert
import com.jishisongfu.duobibi.data.db.PriceAlertDao
import com.jishisongfu.duobibi.data.db.PriceRecord
import com.jishisongfu.duobibi.data.db.PriceRecordDao
import com.jishisongfu.duobibi.floating.NodeDto
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

/** 一次记价的结果(浮窗 / OCR / 手动 共用)。 */
data class RecordOutcome(
    val record: PriceRecord,
    val hitExistingCluster: Boolean,
    val clusterMinPriceBefore: Double?,
    val typicalPrice: Double?,
    /** 本次记价命中并达到目标价的降价雷达条目(用于发本地通知) */
    val triggeredAlerts: List<AlertHit> = emptyList(),
)

/** 浮窗记价命中"想买的"降价雷达(WANT)时的事件。 */
data class AlertHit(
    val alertId: Long,
    val title: String,
    val targetPrice: Double?,
    val newPrice: Double,
    val sourceApp: String,
    val reachedTarget: Boolean,
)

/** 比价擂台一行(一个平台)。 */
data class ArenaRow(
    val platform: String,
    val price: Double,
    val note: String,
    /** true=本机真实记录价(覆盖了 AI 估价),false=AI 估价 */
    val isLocal: Boolean,
)

/** 比价擂台结果:AI 各平台估价 + 本机真实记录覆盖。 */
data class ArenaOutcome(
    val normalizedTitle: String,
    val typicalPrice: Double,
    val rows: List<ArenaRow>,
    val lowestPlatform: String?,
    /** 本机已记录过的同款簇(可点进详情);null = 还没记过 */
    val matchedClusterId: Long?,
    val localMinPrice: Double?,
)

/** 降价雷达批量查价后"本次新触发"的条目(用于发通知)。 */
data class AlertTriggered(
    val id: Long,
    val title: String,
    val platform: String?,
    val mode: String,
    /** anchor:WANT=心理价,BOUGHT=买价 */
    val anchorPrice: Double,
    val currentPrice: Double,
    val savings: Double,
)

@Singleton
class PriceRepository @Inject constructor(
    private val api: ParseApi,
    private val ocrApi: OcrApi,
    private val protectApi: ProtectApi,
    private val arenaApi: ArenaApi,
    private val worthBuyApi: WorthBuyApi,
    private val recordDao: PriceRecordDao,
    private val clusterDao: ClusterDao,
    private val alertDao: PriceAlertDao,
) {

    // ========= 浮窗记价 =========

    suspend fun parseAndInsert(packageName: String, tree: NodeDto?): RecordOutcome {
        val existing = clusterDao.getAll()
        val resp = api.parse(
            ParseRequest(
                packageName = packageName,
                tree = tree,
                clusters = existing.map { ClusterDto(it.id, it.title) },
            )
        )
        return insertParsed(
            existing = existing,
            title = resp.title,
            price = resp.price,
            sourceApp = resp.sourceApp,
            respClusterId = resp.clusterId,
            respTypical = resp.typicalPrice,
        )
    }

    /**
     * OCR 截图记价:复用后端 `/parse-image`(cluster_id 为字符串以兼容 iOS UUID)。
     * Android 端 Long → String 发送,String → Long? 收回并校验本机存在(防 LLM 编造)。
     */
    suspend fun parseImageAndInsert(
        image: okhttp3.MultipartBody.Part,
        clustersJson: okhttp3.RequestBody,
    ): RecordOutcome {
        val existing = clusterDao.getAll()
        val resp = ocrApi.parseImage(image = image, clusters = clustersJson)
        return insertParsed(
            existing = existing,
            title = resp.title,
            price = resp.price,
            sourceApp = resp.sourceApp,
            respClusterId = resp.clusterIdStr?.toLongOrNull(),
            respTypical = resp.typicalPrice,
        )
    }

    private suspend fun insertParsed(
        existing: List<Cluster>,
        title: String,
        price: Double,
        sourceApp: String,
        respClusterId: Long?,
        respTypical: Double?,
    ): RecordOutcome {
        val now = System.currentTimeMillis()
        val targetClusterId: Long
        val hitExisting: Boolean
        val minBefore: Double?

        if (respClusterId != null && existing.any { it.id == respClusterId }) {
            targetClusterId = respClusterId
            hitExisting = true
            minBefore = recordDao.minPriceInCluster(respClusterId)
        } else {
            targetClusterId = clusterDao.insert(
                Cluster(title = title, createdAt = now, typicalPrice = respTypical)
            )
            hitExisting = false
            minBefore = null
        }

        val recordId = recordDao.insert(
            PriceRecord(
                title = title, price = price, sourceApp = sourceApp,
                createdAt = now, clusterId = targetClusterId,
            )
        )
        val record = recordDao.byId(recordId)!!
        val typical = if (hitExisting) clusterDao.byId(targetClusterId)?.typicalPrice else respTypical
        val triggered = checkAlertHits(record, now)
        return RecordOutcome(record, hitExisting, minBefore, typical, triggered)
    }

    /**
     * 浮窗记到价后,扫一遍"想买的"(WANT)降价雷达:标题模糊匹配且 ≤ 心理价 → 触发通知,
     * 并顺手把该雷达的当前价/差价更新。
     */
    private suspend fun checkAlertHits(record: PriceRecord, now: Long): List<AlertHit> {
        val wants = alertDao.getActive().filter { it.mode == AlertMode.WANT }
        if (wants.isEmpty()) return emptyList()
        val hits = mutableListOf<AlertHit>()
        wants.forEach { a ->
            if (!titleFuzzyMatches(a.title, record.title)) return@forEach
            val reached = a.targetPrice == null || record.price <= a.targetPrice
            val anchor = a.targetPrice ?: record.price
            alertDao.updateCheckResult(a.id, now, record.price, "down", anchor - record.price)
            if (reached) {
                hits += AlertHit(a.id, a.title, a.targetPrice, record.price, record.sourceApp, true)
            }
        }
        return hits
    }

    /** 单独插入一条手动记录。clusterId 为 null 时自动新建簇。 */
    suspend fun insertManualRecord(
        title: String,
        price: Double,
        sourceApp: String,
        clusterId: Long? = null,
    ): RecordOutcome {
        val now = System.currentTimeMillis()
        val targetCluster: Long
        val hitExisting: Boolean
        val minBefore: Double?
        if (clusterId != null) {
            targetCluster = clusterId
            hitExisting = true
            minBefore = recordDao.minPriceInCluster(clusterId)
        } else {
            targetCluster = clusterDao.insert(Cluster(title = title, createdAt = now))
            hitExisting = false
            minBefore = null
        }
        val recordId = recordDao.insert(
            PriceRecord(title = title, price = price, sourceApp = sourceApp, createdAt = now, clusterId = targetCluster)
        )
        val record = recordDao.byId(recordId)!!
        val typical = clusterDao.byId(targetCluster)?.typicalPrice
        return RecordOutcome(record, hitExisting, minBefore, typical, checkAlertHits(record, now))
    }

    // ========= 比价擂台 =========

    /** [platforms] 为 null 时后端用默认 4 大电商;品类页选品类后传该品类对应的平台集合。 */
    suspend fun arenaQuote(title: String, platforms: List<String>? = null): ArenaOutcome {
        val resp = arenaApi.arenaQuote(ArenaQuoteRequest(title, platforms))
        val clusters = clusterDao.getAll()
        val matched = clusters.firstOrNull {
            titleFuzzyMatches(resp.title, it.title) || titleFuzzyMatches(it.title, resp.title)
        }
        val localByPlatform: Map<String, Double> = if (matched != null) {
            recordDao.byCluster(matched.id)
                .groupBy { it.sourceApp }
                .mapValues { (_, recs) -> recs.minOf { it.price } }
        } else emptyMap()

        val rows = resp.quotes.map { q ->
            val local = localByPlatform[q.platform]
            if (local != null) ArenaRow(q.platform, local, "本机记录最低", isLocal = true)
            else ArenaRow(q.platform, q.price, q.note, isLocal = false)
        }
        val lowest = rows.minByOrNull { it.price }?.platform
        return ArenaOutcome(
            normalizedTitle = resp.title,
            typicalPrice = resp.typicalPrice,
            rows = rows,
            lowestPlatform = lowest,
            matchedClusterId = matched?.id,
            localMinPrice = localByPlatform.values.minOrNull(),
        )
    }

    // ========= AI 值不值得买 =========

    suspend fun worthBuy(title: String, price: Double, platform: String? = null): WorthBuyResponse =
        worthBuyApi.worthBuy(WorthBuyRequest(title = title, price = price, platform = platform))

    // ========= 价格记录 / 商品簇 =========

    fun observeAllRecords(): Flow<List<PriceRecord>> = recordDao.observeAll()
    fun searchRecords(q: String): Flow<List<PriceRecord>> = recordDao.search(q)
    suspend fun recordById(id: Long): PriceRecord? = recordDao.byId(id)
    suspend fun deleteRecord(id: Long) = recordDao.delete(id)
    fun observeRecordsByCluster(clusterId: Long): Flow<List<PriceRecord>> =
        recordDao.observeByCluster(clusterId)

    fun observeAllClusters(): Flow<List<Cluster>> = clusterDao.observeAll()
    suspend fun currentClusters(): List<Cluster> = clusterDao.getAll()
    suspend fun clusterById(id: Long): Cluster? = clusterDao.byId(id)
    suspend fun renameCluster(id: Long, title: String) = clusterDao.rename(id, title)

    suspend fun deleteCluster(id: Long) {
        recordDao.deleteByCluster(id)
        clusterDao.delete(id)
    }

    suspend fun moveRecordToCluster(recordId: Long, newClusterId: Long) {
        val record = recordDao.byId(recordId) ?: return
        val originalClusterId = record.clusterId
        recordDao.moveRecord(recordId, newClusterId)
        if (originalClusterId != newClusterId && recordDao.countInCluster(originalClusterId) == 0) {
            clusterDao.delete(originalClusterId)
        }
    }

    suspend fun splitRecordToNewCluster(recordId: Long): Long? {
        val record = recordDao.byId(recordId) ?: return null
        val originalClusterId = record.clusterId
        val newClusterId = clusterDao.insert(Cluster(title = record.title, createdAt = System.currentTimeMillis()))
        recordDao.moveRecord(recordId, newClusterId)
        if (recordDao.countInCluster(originalClusterId) == 0) clusterDao.delete(originalClusterId)
        return newClusterId
    }

    // ========= 降价提醒雷达 =========

    fun observeAllAlerts(): Flow<List<PriceAlert>> = alertDao.observeAll()
    fun observeAlertsByMode(mode: String): Flow<List<PriceAlert>> = alertDao.observeByMode(mode)
    fun observeActiveAlertCount(): Flow<Int> = alertDao.observeActiveCount()
    fun observeReadyAlertCount(): Flow<Int> = alertDao.observeReadyCount()
    suspend fun alertById(id: Long): PriceAlert? = alertDao.byId(id)

    suspend fun addWantAlert(title: String, platform: String?, targetPrice: Double?): Long {
        val now = System.currentTimeMillis()
        return alertDao.insert(
            PriceAlert(
                title = title.trim(),
                platform = platform?.trim()?.takeIf { it.isNotEmpty() },
                mode = AlertMode.WANT,
                targetPrice = targetPrice,
                createdAt = now,
            )
        )
    }

    suspend fun addBoughtAlert(title: String, platform: String, purchasePrice: Double, purchaseAt: Long): Long {
        val now = System.currentTimeMillis()
        return alertDao.insert(
            PriceAlert(
                title = title.trim(),
                platform = platform.trim(),
                mode = AlertMode.BOUGHT,
                purchasePrice = purchasePrice,
                purchaseAt = purchaseAt,
                createdAt = now,
            )
        )
    }

    suspend fun updateAlert(item: PriceAlert) = alertDao.update(item)
    suspend fun setAlertStatus(id: Long, status: String) = alertDao.setStatus(id, status)
    suspend fun deleteAlert(id: Long) = alertDao.delete(id)

    /**
     * 批量遍历 active 雷达,逐条调后端 track-price 更新当前价 + 差价。
     * 返回"本次新触发"(savings 从 ≤0 变 >0)的条目给调用方发通知。单条失败跳过。
     */
    suspend fun batchCheckAlerts(): List<AlertTriggered> {
        val triggered = mutableListOf<AlertTriggered>()
        val items = alertDao.getActive()
        for (a in items) {
            val anchor = when (a.mode) {
                AlertMode.BOUGHT -> a.purchasePrice ?: 0.0
                else -> a.targetPrice ?: 0.0
            }
            if (anchor <= 0.0) continue  // WANT 无心理价时无法判断到价,跳过
            val prev = a.lastSavings ?: 0.0
            try {
                val resp = protectApi.trackPrice(
                    TrackPriceRequest(
                        platform = a.platform ?: "主流电商",
                        productTitle = a.title,
                        purchasePrice = anchor,
                        purchaseAt = (a.purchaseAt ?: a.createdAt) / 1000,
                    )
                )
                alertDao.updateCheckResult(a.id, System.currentTimeMillis(), resp.currentPrice, resp.trend, resp.savings)
                if (resp.savings > 0.0 && prev <= 0.0) {
                    triggered += AlertTriggered(
                        id = a.id, title = a.title, platform = a.platform, mode = a.mode,
                        anchorPrice = anchor, currentPrice = resp.currentPrice, savings = resp.savings,
                    )
                }
            } catch (_: Exception) {
                // 单条失败不影响其他,下次再试
            }
        }
        return triggered
    }

    // ========= 全清 =========

    suspend fun deleteAll() {
        recordDao.deleteAll()
        clusterDao.deleteAll()
        alertDao.deleteAll()
    }

    // ========= 示例数据(占审用)=========

    suspend fun hasAnyRealData(): Boolean = clusterDao.countReal() > 0

    suspend fun clearDemoData() {
        recordDao.deleteDemo()
        clusterDao.deleteDemo()
        alertDao.deleteDemo()
    }

    /**
     * 注入示例数据:4 个商品簇(跨平台、价差合理)+ 多条记录(散布近 30 天)+ 3 条降价雷达。
     * 全部 isDemo=true,清除时按此字段批量删,真实数据不受影响。
     */
    suspend fun seedDemoData() {
        val now = System.currentTimeMillis()
        val dayMs = 24L * 3600 * 1000

        val iphoneId = clusterDao.insert(
            Cluster(title = "iPhone 15 Pro 256GB 原色钛金属", createdAt = now - 26 * dayMs,
                typicalPrice = 8299.0, isDemo = true)
        )
        listOf(Triple(7799.0, "京东", 2), Triple(8099.0, "淘宝", 7),
            Triple(7888.0, "拼多多", 13), Triple(8299.0, "抖音", 20)).forEach { (p, app, d) ->
            recordDao.insert(PriceRecord(title = "iPhone 15 Pro 256GB 原色钛金属", price = p,
                sourceApp = app, createdAt = now - d * dayMs, clusterId = iphoneId, isDemo = true))
        }

        val dysonId = clusterDao.insert(
            Cluster(title = "戴森 Dyson HD15 吹风机", createdAt = now - 22 * dayMs,
                typicalPrice = 2399.0, isDemo = true)
        )
        listOf(Triple(2199.0, "京东", 3), Triple(2299.0, "抖音", 9), Triple(2399.0, "淘宝", 17)).forEach { (p, app, d) ->
            recordDao.insert(PriceRecord(title = "戴森 Dyson HD15 吹风机", price = p,
                sourceApp = app, createdAt = now - d * dayMs, clusterId = dysonId, isDemo = true))
        }

        val coffeeId = clusterDao.insert(
            Cluster(title = "雀巢多趣酷思咖啡胶囊 16 颗", createdAt = now - 18 * dayMs,
                typicalPrice = 99.0, isDemo = true)
        )
        listOf(Triple(79.0, "拼多多", 1), Triple(89.0, "京东", 6), Triple(99.0, "淘宝", 12)).forEach { (p, app, d) ->
            recordDao.insert(PriceRecord(title = "雀巢多趣酷思咖啡胶囊 16 颗", price = p,
                sourceApp = app, createdAt = now - d * dayMs, clusterId = coffeeId, isDemo = true))
        }

        val switchId = clusterDao.insert(
            Cluster(title = "任天堂 Switch OLED 续航增强版", createdAt = now - 12 * dayMs,
                typicalPrice = 2249.0, isDemo = true)
        )
        listOf(Triple(2099.0, "拼多多", 4), Triple(2199.0, "京东", 8), Triple(2249.0, "抖音", 14)).forEach { (p, app, d) ->
            recordDao.insert(PriceRecord(title = "任天堂 Switch OLED 续航增强版", price = p,
                sourceApp = app, createdAt = now - d * dayMs, clusterId = switchId, isDemo = true))
        }

        // 降价雷达:2 条想买的(WANT) + 1 条已买的(BOUGHT),已买的预填可申请价保
        alertDao.insert(PriceAlert(
            title = "iPhone 16 Pro Max 256GB", platform = "京东", mode = AlertMode.WANT,
            targetPrice = 8888.0, createdAt = now - 8 * dayMs, isDemo = true,
        ))
        alertDao.insert(PriceAlert(
            title = "AirPods Pro 2 USB-C", platform = null, mode = AlertMode.WANT,
            targetPrice = 1399.0, createdAt = now - 5 * dayMs,
            lastCheckedAt = now - dayMs, lastCurrentPrice = 1499.0, lastTrend = "flat", lastSavings = -100.0,
            isDemo = true,
        ))
        alertDao.insert(PriceAlert(
            title = "戴森 Dyson HD15 吹风机", platform = "京东", mode = AlertMode.BOUGHT,
            purchasePrice = 2399.0, purchaseAt = now - 6 * dayMs, createdAt = now - 6 * dayMs,
            lastCheckedAt = now - dayMs, lastCurrentPrice = 2199.0, lastTrend = "down", lastSavings = 200.0,
            isDemo = true,
        ))
    }
}

/**
 * 简易标题模糊匹配 —— 占坑期最简版,纯本地不调 LLM。
 * 把标题按空白/标点切 token,过滤太短的(<2),≥60% 的 token 命中即算匹配。
 */
internal fun titleFuzzyMatches(queryTitle: String, targetTitle: String): Boolean {
    val tokens = queryTitle.lowercase().split(Regex("[\\s\\p{Punct}]+"))
        .map { it.trim() }.filter { it.length >= 2 }
    if (tokens.isEmpty()) return false
    val target = targetTitle.lowercase()
    val hits = tokens.count { target.contains(it) }
    val threshold = maxOf(1, (tokens.size * 0.6).toInt())
    return hits >= threshold
}
