package theorycrafter.ui.fiteditor

import androidx.compose.runtime.Composable
import eve.data.ModuleSlotType
import eve.data.ModuleType
import theorycrafter.TheorycrafterContext
import theorycrafter.TheorycrafterContext.eveData
import theorycrafter.TheorycrafterContext.fits
import theorycrafter.TheorycrafterTest
import theorycrafter.fitting.Fit
import theorycrafter.fitting.FittingEngine
import theorycrafter.runBlockingTest
import theorycrafter.setApplicationContent
import theorycrafter.utils.AutoSuggest
import kotlin.test.*

/**
 * Tests the auto-suggest in the fit editor.
 */
class AutoSuggestTest: TheorycrafterTest() {


    /**
     * Asserts that the suggestion at [index], when searching for [text] is [expected].
     */
    private suspend fun <T> AutoSuggest<T>.assertSuggestionIs(text: String, index: Int = 0, expected: T) {
        val suggestions = this(text)
        if ((suggestions == null) || (index > suggestions.lastIndex))
            fail("The auto-suggest has fewer than ${index + 1} item(s)")

        assertEquals(expected, suggestions[index])
    }


    /**
     * Asserts that the suggestions, when searching for [text], contain [expected].
     */
    private suspend fun <T> AutoSuggest<T>.assertSuggestionsContain(text: String, expected: T) {
        val suggestions = this(text)
        if (suggestions == null)
            fail("Auto-suggest returned no results")

        assertContains(suggestions, expected)
    }


    /**
     * Sets up an [AutoSuggest] for the ship with the given name and calls [testBlock] with it.
     */
    private suspend fun <T> testAutoSuggest(
        shipName: String,
        extraFitting: (FittingEngine.ModificationScope.(Fit) -> Unit)? = null,
        autoSuggestProvider: @Composable TheorycrafterContext.(Fit) -> AutoSuggest<T>,
        testBlock: suspend AutoSuggest<T>.() -> Unit
    ) {
        val fit = newFit(shipName)
        if (extraFitting != null) {
            fits.modifyAndSave {
                extraFitting(fit)
            }
        }

        lateinit var autoSuggest: AutoSuggest<T>

        rule.setApplicationContent {
            autoSuggest = TheorycrafterContext.autoSuggestProvider(fit)
        }

        autoSuggest.testBlock()
    }


    /**
     * Some non-specific medslot auto-suggest tests.
     */
    @Test
    fun medslotTests() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForModules(fit.ship, ModuleSlotType.MEDIUM) },
        ) {

            suspend fun assertFirstSuggestionIs(text: String, name: String) {
                assertSuggestionIs(text, expected = eveData.moduleType(name))
            }

            assertFirstSuggestionIs("50mn", "50MN Microwarpdrive II")
            assertFirstSuggestionIs("s capa", "Small Capacitor Booster II")
            assertFirstSuggestionIs("m capa", "Medium Capacitor Booster II")
            assertFirstSuggestionIs("h capa", "Heavy Capacitor Booster II")
        }
    }


    /**
     * Some non-specific drone auto-suggest tests.
     */
    @Test
    fun droneTests() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForDroneTypes(fit) }
        ) {

            suspend fun assertFirstSuggestionIs(text: String, name: String) {
                assertSuggestionIs(text, expected = eveData.droneType(name))
            }

            assertFirstSuggestionIs("l armor", "Light Armor Maintenance Bot II")
            assertFirstSuggestionIs("m armor", "Medium Armor Maintenance Bot II")
            assertFirstSuggestionIs("h armor", "Heavy Armor Maintenance Bot II")
        }
    }


    /**
     * Some non-specific cargohold tests.
     */
    @Test
    fun cargoTests() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { autoSuggest.cargoItemTypes }
        ) {

            suspend fun assertFirstSuggestionIs(text: String, name: String) {
                assertSuggestionIs(text, expected = eveData.cargoItemType(name))
            }

            assertFirstSuggestionIs("pas", "Nanite Repair Paste")
        }
    }


    /**
     * Some non-specific charge tests.
     */
    @Test
    fun chargeTests() = runBlockingTest {
        val heavyPulseLaser = eveData.moduleType("Heavy Pulse Laser II")

        testAutoSuggest(
            shipName = "Zealot",
            extraFitting = { fit ->
                fit.fitModule(heavyPulseLaser, 0)
            },
            autoSuggestProvider = { autoSuggest.rememberForChargeTypes(heavyPulseLaser) }
        ) {

            suspend fun assertFirstSuggestionIs(text: String, name: String) {
                assertSuggestionIs(text, expected = eveData.chargeType(name))
            }

            assertFirstSuggestionIs("radio", "Imperial Navy Radio M")
        }
    }


    /**
     * Asserts that the auto-suggest prefers the given module when searching with the given query
     */
    private suspend fun AutoSuggest<ModuleType>.assertPrefers(
        text: String,
        preferred: String,
        over: String
    ) {
        assertSuggestionIs(text, expected = eveData.moduleType(preferred))
        assertSuggestionsContain(text, expected = eveData.moduleType(over))
    }


    /**
     * Test a set of searches where we know what should be preferred when fitting into a med slot.
     */
    @Test
    fun medSlotPreferencesTest() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForModules(fit.ship, ModuleSlotType.MEDIUM) },
        ) {
            assertPrefers(
                text = "tracking",
                preferred = "Tracking Computer II",
                over = "Omnidirectional Tracking Link II"
            )
            assertPrefers(
                text = "warp s",
                preferred = "Warp Scrambler II",
                over = "Heavy Warp Scrambler II"
            )
        }
    }


    /**
     * Test a set of searches where we know what should be preferred when fitting into a low slot.
     */
    @Test
    fun lowSlotPreferencesTest() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForModules(fit.ship, ModuleSlotType.LOW) },
        ) {
            assertPrefers(
                text = "da",
                preferred = "Damage Control II",
                over = "Dark Blood EM Coating"
            )
        }
    }



    /**
     * Test that using the exact name of a module prefers it over another, even if it's a substring.
     */
    @Test
    @Ignore  // This currently fails
    fun exactMatchIsPreferred() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForModules(fit.ship, ModuleSlotType.HIGH) },
        ) {
            assertPrefers(
                text = "Small Energy Neutralizer I",
                preferred = "Small Energy Neutralizer I",
                over = "Small Energy Neutralizer II"
            )
        }
    }


    /**
     * Test that a shorter match is preferred.
     *
     * Enabling this currently conflicts with matching "Damage Control" over "Dark Blood ..." when searching for "da",
     * and that's more important.
     */
    @Test
    @Ignore
    fun shorterMatchIsPreferred() = runBlockingTest {
        testAutoSuggest(
            shipName = "Vexor",
            autoSuggestProvider = { fit -> autoSuggest.rememberForModules(fit.ship, ModuleSlotType.MEDIUM) },
        ) {
            assertPrefers(
                text = "50",
                preferred = "50MN Microwarpdrive II",
                over = "500MN Microwarpdrive II"
            )
            assertPrefers(
                text = "10",
                preferred = "10MN Afterburner II",
                over = "100MN Afterburner II"
            )
        }
    }


}