package com.fabledsword.minstrel.library.data

import app.cash.turbine.test
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.cache.db.entities.CachedArtistEntity
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import retrofit2.Retrofit
import kotlinx.serialization.json.Json
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import okhttp3.MediaType.Companion.toMediaType
import kotlin.test.assertEquals

class LibraryRepositoryTest {
    private lateinit var server: MockWebServer
    private lateinit var retrofit: Retrofit
    private val artistDao = mockk<CachedArtistDao>(relaxed = true)
    private val albumDao = mockk<CachedAlbumDao>(relaxed = true)
    private val trackDao = mockk<CachedTrackDao>(relaxed = true)

    @BeforeEach
    fun setup() {
        server = MockWebServer().apply { start() }
        retrofit =
            Retrofit.Builder()
                .baseUrl(server.url("/"))
                .client(OkHttpClient.Builder().build())
                .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
                .build()
    }

    private companion object {
        private val json = Json { ignoreUnknownKeys = true }
    }

    @AfterEach
    fun teardown() {
        server.shutdown()
    }

    @Test
    fun `observeArtists maps DAO rows to domain ArtistRefs`() = runTest {
        every { artistDao.observeAll() } returns
            flowOf(
                listOf(
                    CachedArtistEntity(id = "a1", name = "Boards of Canada", sortName = "Boards of Canada"),
                ),
            )
        // observeArtists now combines with albums (to derive the tile
        // cover from the first album); stub it so combine can emit.
        every { albumDao.observeAll() } returns flowOf(emptyList())
        val repo = LibraryRepository(artistDao, albumDao, trackDao, retrofit)

        repo.observeArtists().test {
            val first = awaitItem()
            assertEquals(1, first.size)
            assertEquals("a1", first[0].id)
            assertEquals("Boards of Canada", first[0].name)
            awaitComplete()
        }
    }

    @Test
    fun `refreshArtistDetail upserts the artist + the embedded albums`() = runTest {
        server.enqueue(
            MockResponse()
                .setResponseCode(200)
                .setHeader("Content-Type", "application/json")
                .setBody(
                    """
                    {
                      "id": "a1",
                      "name": "Boards of Canada",
                      "sort_name": "Boards of Canada",
                      "album_count": 2,
                      "cover_url": "",
                      "albums": [
                        {"id": "al1", "title": "Music Has the Right to Children", "artist_id": "a1", "sort_title": "Music Has the Right to Children"},
                        {"id": "al2", "title": "Geogaddi", "artist_id": "a1", "sort_title": "Geogaddi"}
                      ]
                    }
                    """.trimIndent(),
                ),
        )
        coEvery { artistDao.upsertAll(any()) } returns Unit
        coEvery { albumDao.upsertAll(any()) } returns Unit
        val repo = LibraryRepository(artistDao, albumDao, trackDao, retrofit)

        repo.refreshArtistDetail("a1")

        coVerify { artistDao.upsertAll(match { it.size == 1 && it[0].id == "a1" }) }
        coVerify { albumDao.upsertAll(match { it.size == 2 && it[0].id == "al1" && it[1].id == "al2" }) }
    }

    @Test
    fun `getArtist returns null when DAO is empty`() = runTest {
        coEvery { artistDao.getById("missing") } returns null
        val repo = LibraryRepository(artistDao, albumDao, trackDao, retrofit)

        val result = repo.getArtist("missing")

        assertEquals(null, result)
    }
}
