package com.ponzischeme89.memby.ui.search

import com.ponzischeme89.memby.data.model.BaseItem
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class SearchRankingTest {

    private fun item(name: String, series: String? = null, id: String = name) =
        BaseItem(id = id, name = name, seriesName = series)

    private fun names(items: List<BaseItem>) = items.map { it.name }

    @Test
    fun `an exact title beats a title that merely starts with the query`() {
        val ranked = rankSearchResults(
            "dune",
            listOf(item("Dune: Part Two"), item("Dunes of Mars"), item("Dune")),
        )
        assertEquals("Dune", ranked.first().name)
    }

    @Test
    fun `titles starting with the query come before mid-word matches`() {
        val ranked = rankSearchResults(
            "star",
            listOf(item("Costar"), item("Lone Star"), item("Stargate")),
        )
        // Prefix first, then the word-boundary match, then the buried one.
        assertEquals(listOf("Stargate", "Lone Star", "Costar"), names(ranked))
    }

    @Test
    fun `an episode is found by its series name when its own title does not match`() {
        val ranked = rankSearchResults(
            "severance",
            listOf(item("Defiant Jazz", series = "Severance"), item("Severed Ties")),
        )
        assertEquals("Defiant Jazz", ranked.first().name)
    }

    @Test
    fun `weaker matches are kept rather than dropped`() {
        // A genre or overview match the backend returned: last, but still there, because
        // "no exact match but here is something related" beats an empty pane.
        val ranked = rankSearchResults("comedy", listOf(item("Some Unrelated Title")))
        assertEquals(1, ranked.size)
    }

    @Test
    fun `ranking is stable so the backend's own relevance order survives within a tier`() {
        val backendOrder = listOf(item("Alien 3"), item("Alien"), item("Aliens"))
        val ranked = rankSearchResults("alien", backendOrder)
        // "Alien" is the exact match and is lifted; the other two keep the order the
        // server chose rather than being re-sorted alphabetically.
        assertEquals(listOf("Alien", "Alien 3", "Aliens"), names(ranked))
    }

    @Test
    fun `an empty query leaves the list untouched`() {
        val items = listOf(item("B"), item("A"))
        assertEquals(items, rankSearchResults("   ", items))
    }

    @Test
    fun `searching starts at two characters`() {
        assertFalse(shouldSearch(""))
        assertFalse(shouldSearch("a"))
        assertFalse(shouldSearch("  a  "))
        assertTrue(shouldSearch("ab"))
        assertTrue(shouldSearch(" the wire "))
    }

    @Test
    fun `gateway scored order can be preserved by callers`() {
        val backendOrder = listOf(
            BaseItem(id = "personal", name = "Dune Messiah", membyRecommendationScore = 9.0),
            BaseItem(id = "exact", name = "Dune", membyRecommendationScore = 8.0),
        )
        // Scored gateway results are intentionally not passed through rankSearchResults.
        val displayed = if (backendOrder.any { it.membyRecommendationScore != null }) {
            backendOrder
        } else {
            rankSearchResults("dune", backendOrder)
        }
        assertEquals(listOf("Dune Messiah", "Dune"), names(displayed))
    }
}
