package com.fabledsword.minstrel.metadata

import com.fabledsword.minstrel.cache.db.dao.CachedAlbumDao
import com.fabledsword.minstrel.cache.db.dao.CachedArtistDao
import com.fabledsword.minstrel.cache.db.dao.CachedTrackDao
import com.fabledsword.minstrel.di.ApplicationScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import javax.inject.Inject
import javax.inject.Singleton

private const val SWEEP_INTERVAL_MS = 30L * 60 * 1000 // 30 minutes
private const val STALENESS_MS = 24L * 60 * 60 * 1000 // 24 hours
private const val SWEEP_BATCH_SIZE = 100
private const val SWEEP_CONCURRENCY = 4

/**
 * Background freshness pass over [MetadataProvider]. Wakes every
 * [SWEEP_INTERVAL_MS] and re-fetches up to [SWEEP_BATCH_SIZE]
 * cached_album / cached_artist / cached_track rows whose
 * `fetchedAt` is older than [STALENESS_MS]. Reuses
 * [MetadataProvider.refreshAlbum] / refreshArtist / refreshTrack —
 * dedup applies, so a sweep that overlaps with a user-driven
 * on-miss fetch is a no-op on the duplicated ID.
 *
 * Bounded by [SWEEP_CONCURRENCY] in-flight requests so we don't
 * saturate the connection pool. Failures are tolerated per-row —
 * one bad ID doesn't stop the batch.
 *
 * Constructed at app launch via the construct-the-singleton trick
 * in [com.fabledsword.minstrel.MinstrelApplication].
 */
@Singleton
class FreshnessSweeper @Inject constructor(
    private val albumDao: CachedAlbumDao,
    private val artistDao: CachedArtistDao,
    private val trackDao: CachedTrackDao,
    private val metadataProvider: MetadataProvider,
    @ApplicationScope private val appScope: CoroutineScope,
) {
    init {
        appScope.launch {
            while (true) {
                sweep()
                delay(SWEEP_INTERVAL_MS)
            }
        }
    }

    private suspend fun sweep() {
        val cutoff = System.currentTimeMillis() - STALENESS_MS
        val sem = Semaphore(SWEEP_CONCURRENCY)
        sweepBucket(albumDao.idsStaleBefore(cutoff, SWEEP_BATCH_SIZE), sem) { id ->
            metadataProvider.refreshAlbum(id).join()
        }
        sweepBucket(artistDao.idsStaleBefore(cutoff, SWEEP_BATCH_SIZE), sem) { id ->
            metadataProvider.refreshArtist(id).join()
        }
        sweepBucket(trackDao.idsStaleBefore(cutoff, SWEEP_BATCH_SIZE), sem) { id ->
            metadataProvider.refreshTrack(id).join()
        }
    }

    private suspend fun sweepBucket(
        ids: List<String>,
        sem: Semaphore,
        refresh: suspend (String) -> Unit,
    ) {
        ids.forEach { id ->
            sem.withPermit { refresh(id) }
        }
    }
}
