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.library.data.LibraryRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertTrue

class MetadataProviderTest {
    private val albumDao = mockk<CachedAlbumDao>(relaxed = true)
    private val artistDao = mockk<CachedArtistDao>(relaxed = true)
    private val trackDao = mockk<CachedTrackDao>(relaxed = true)
    private val library = mockk<LibraryRepository>(relaxed = true)

    @Test
    fun `same missing id fetched once (dedup)`() = runTest {
        val scope = this
        coEvery { albumDao.observeById("a1") } returns flowOf(null)
        val provider = MetadataProvider(albumDao, artistDao, trackDao, library, scope)

        provider.observeAlbum("a1").first()
        provider.observeAlbum("a1").first()
        testScheduler.advanceUntilIdle()

        coVerify(exactly = 1) { library.refreshAlbumDetail("a1") }
    }

    @Test
    fun `at most four fetches run concurrently`() = runTest {
        val scope = this
        val live = AtomicInteger(0)
        val peak = AtomicInteger(0)
        val gate = CompletableDeferred<Unit>()
        coEvery { library.refreshAlbumDetail(any()) } coAnswers {
            val now = live.incrementAndGet()
            peak.updateAndGet { p -> maxOf(p, now) }
            gate.await()
            live.decrementAndGet()
            mockk(relaxed = true)
        }
        (1..10).forEach { i -> coEvery { albumDao.observeById("a$i") } returns flowOf(null) }
        val provider = MetadataProvider(albumDao, artistDao, trackDao, library, scope)

        (1..10).forEach { i ->
            launch(UnconfinedTestDispatcher(testScheduler)) {
                provider.observeAlbum("a$i").first()
            }
        }
        testScheduler.advanceUntilIdle()

        assertTrue(peak.get() <= 4, "peak concurrency was ${peak.get()}, expected <= 4")
        gate.complete(Unit)
        testScheduler.advanceUntilIdle()
    }
}
