package theorycrafter.ui.fiteditor

import androidx.compose.runtime.Composable
import androidx.compose.ui.test.ComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runDesktopComposeUiTest
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.setApplicationContent
import theorycrafter.utils.AutoSuggest
import kotlin.test.*

/**
 * Tests the auto-suggest in the fit editor.
 */
@OptIn(ExperimentalTestApi::class)
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> ComposeUiTest.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>

        setApplicationContent {
            autoSuggest = TheorycrafterContext.autoSuggestProvider(fit)
        }

        autoSuggest.testBlock()
    }


    /**
     * Some non-specific medslot auto-suggest tests.
     */
    @Test
    fun medslotTests() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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() = runDesktopComposeUiTest {
        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"
            )
        }
    }


}