package com.fabledsword.minstrel.search.data

import com.fabledsword.minstrel.api.endpoints.SearchApi
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.connectivity.NetworkStatusController
import com.fabledsword.minstrel.connectivity.ServerHealth
import com.fabledsword.minstrel.library.data.toDomain
import com.fabledsword.minstrel.models.SearchResponseRef
import retrofit2.Retrofit
import retrofit2.create
import javax.inject.Inject
import javax.inject.Singleton

private const val LOCAL_SEARCH_LIMIT = 20

/**
 * Cache-first when offline. When [NetworkStatusController] reports Healthy or
 * Unstable the repository hits `/api/search` and returns the server's three-facet
 * paged response. When health is Offline or ServerDown it falls back to
 * Room LIKE queries against `cached_*` so the user can still find
 * something to play from what's already on the device; the outcome's
 * [SearchOutcome.localOnly] flag lets the screen draw an "offline
 * results" hint instead of pretending the server answered.
 */
@Singleton
class SearchRepository @Inject constructor(
    retrofit: Retrofit,
    private val serverHealth: NetworkStatusController,
    private val trackDao: CachedTrackDao,
    private val albumDao: CachedAlbumDao,
    private val artistDao: CachedArtistDao,
) {
    private val api: SearchApi = retrofit.create()

    suspend fun search(query: String): SearchOutcome = when (serverHealth.state.value) {
        ServerHealth.Healthy, ServerHealth.Unstable ->
            SearchOutcome(remoteSearch(query), localOnly = false)
        ServerHealth.Offline, ServerHealth.ServerDown ->
            SearchOutcome(localSearch(query), localOnly = true)
    }

    private suspend fun remoteSearch(query: String): SearchResponseRef {
        val wire = api.search(query)
        return SearchResponseRef(
            artists = wire.artists.items.map { it.toDomain() },
            albums = wire.albums.items.map { it.toDomain() },
            tracks = wire.tracks.items.map { it.toDomain() },
        )
    }

    private suspend fun localSearch(query: String): SearchResponseRef = SearchResponseRef(
        artists = artistDao.searchByName(query, LOCAL_SEARCH_LIMIT).map { it.toDomain() },
        albums = albumDao.searchByTitle(query, LOCAL_SEARCH_LIMIT).map { it.toDomain() },
        tracks = trackDao.searchByTitle(query, LOCAL_SEARCH_LIMIT).map { it.toDomain() },
    )
}

/**
 * Wraps the search response with the signal of whether the result came
 * from the server or from the local cached entities. The screen renders
 * the same SearchResponseRef either way; the flag drives the offline
 * banner copy.
 */
data class SearchOutcome(
    val response: SearchResponseRef,
    val localOnly: Boolean,
)
