package dev.ulfrx.recipe.ui.screens.mealplaneditor

import dev.ulfrx.recipe.ui.components.recipe.MealSlot
import dev.ulfrx.recipe.ui.components.recipe.RecipeIngredientOptionUi
import dev.ulfrx.recipe.ui.components.recipe.RecipeIngredientSlotUi
import dev.ulfrx.recipe.ui.components.recipe.RecipeNutritionUi
import dev.ulfrx.recipe.ui.screens.recipedetail.RecipeDetailUi
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue

class MealPlanEditorViewModelTest {
    @Test
    fun opensWithDefaultsDerivedFromRecipe() {
        val viewModel = MealPlanEditorViewModel()

        viewModel.open(recipe = recipe(allowedSlots = listOf(MealSlot.Lunch, MealSlot.Dinner)))

        val state = viewModel.editing()
        assertEquals(MealSlot.Lunch, state.selectedSlot)
        assertEquals(MIN_PLAN_SERVINGS, state.servings)
        assertTrue(state.substitutions.isEmpty())
        assertTrue(state.excludedIngredients.isEmpty())
        assertTrue(state.addedIngredients.isEmpty())
    }

    @Test
    fun initialSubstitutionsAreSanitizedAgainstRecipe() {
        val viewModel = MealPlanEditorViewModel()
        val recipe = recipe(allowedSlots = listOf(MealSlot.Breakfast))
        val validSlot = recipe.ingredients.first { it.alternatives.isNotEmpty() }
        val validOption = validSlot.alternatives.first()

        viewModel.open(
            recipe = recipe,
            initialSubstitutions = mapOf(
                validSlot.id to validOption.id,
                "unknown-slot" to "anything",
                validSlot.id + "-x" to validOption.id,
            ),
        )

        val state = viewModel.editing()
        assertEquals(mapOf(validSlot.id to validOption.id), state.substitutions)
    }

    @Test
    fun closeResetsStateToHidden() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())

        viewModel.close()

        assertEquals(MealPlanEditorState.Hidden, viewModel.state.value)
    }

    @Test
    fun confirmReturnsPayloadAndClosesEditor() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe(allowedSlots = listOf(MealSlot.Breakfast, MealSlot.Lunch)))
        viewModel.setServings(4)
        viewModel.selectSlot(MealSlot.Lunch)
        viewModel.selectDate(LocalDate(2026, 7, 1))

        val payload = viewModel.confirm()

        assertNotNull(payload)
        assertEquals(4, payload.servings)
        assertEquals(MealSlot.Lunch, payload.slot)
        assertEquals(LocalDate(2026, 7, 1), payload.date)
        assertEquals(MealPlanEditorState.Hidden, viewModel.state.value)
    }

    @Test
    fun confirmReturnsNullWhenHidden() {
        val viewModel = MealPlanEditorViewModel()

        assertNull(viewModel.confirm())
    }

    @Test
    fun servingsAreClampedToSupportedRange() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())

        viewModel.setServings(0)
        assertEquals(MIN_PLAN_SERVINGS, viewModel.editing().servings)

        viewModel.setServings(MAX_PLAN_SERVINGS + 5)
        assertEquals(MAX_PLAN_SERVINGS, viewModel.editing().servings)
    }

    @Test
    fun selectSlotIgnoresValuesOutsideAllowedSet() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe(allowedSlots = listOf(MealSlot.Breakfast)))

        viewModel.selectSlot(MealSlot.Dinner)

        assertEquals(MealSlot.Breakfast, viewModel.editing().selectedSlot)
    }

    @Test
    fun substitutionTogglesByPickingDefaultAgain() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())
        val slot = viewModel.editing().recipe.ingredients.first { it.alternatives.isNotEmpty() }
        val alt = slot.alternatives.first()

        viewModel.selectSubstitution(slot.id, alt.id)
        assertEquals(alt.id, viewModel.editing().substitutions[slot.id])

        viewModel.selectSubstitution(slot.id, slot.default.id)
        assertFalse(slot.id in viewModel.editing().substitutions)
    }

    @Test
    fun removeAndRestoreCycleClearsExclusions() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())
        val slot = viewModel.editing().recipe.ingredients.first()

        viewModel.removeRecipeIngredient(slot.id)
        assertTrue(slot.id in viewModel.editing().excludedIngredients)

        viewModel.restoreRemovedIngredients()
        assertTrue(viewModel.editing().excludedIngredients.isEmpty())
    }

    @Test
    fun addingIngredientAppendsToEditingList() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())
        val candidate = addable("ing_test", "Test składnik")

        viewModel.addIngredient(candidate)

        val state = viewModel.editing()
        assertEquals(1, state.addedIngredients.size)
        assertEquals(candidate.ingredientId, state.addedIngredients.first().ingredientId)
    }

    @Test
    fun addingDuplicateIngredientIsIgnored() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())
        val candidate = addable("ing_test", "Test składnik")

        viewModel.addIngredient(candidate)
        viewModel.addIngredient(candidate)

        assertEquals(1, viewModel.editing().addedIngredients.size)
    }

    @Test
    fun removeAddedDropsByIngredientId() {
        val viewModel = MealPlanEditorViewModel()
        viewModel.open(recipe = recipe())
        viewModel.addIngredient(addable("ing_a", "A"))
        viewModel.addIngredient(addable("ing_b", "B"))

        viewModel.removeAddedIngredient("ing_a")

        val remaining = viewModel.editing().addedIngredients.map { it.ingredientId }
        assertEquals(listOf("ing_b"), remaining)
    }

    private fun MealPlanEditorViewModel.editing(): MealPlanEditorState.Editing {
        val state = state.value
        assertTrue(state is MealPlanEditorState.Editing)
        return state
    }

    private fun recipe(
        allowedSlots: List<MealSlot> = listOf(MealSlot.Breakfast, MealSlot.Snack),
    ): RecipeDetailUi =
        RecipeDetailUi(
            id = "test_recipe",
            title = "Test recipe",
            cookingMinutes = 15,
            nutrition = RecipeNutritionUi(kcal = 300, protein = 10, fat = 8, carbs = 40),
            allowedSlots = allowedSlots,
            steps = listOf("Krok 1.", "Krok 2."),
            ingredients =
                listOf(
                    slot(
                        id = "slot_main",
                        name = "Płatki",
                        amount = 60.0,
                        unit = "g",
                        alternatives = listOf("Płatki górskie" to 60.0, "Płatki jaglane" to 60.0),
                    ),
                    slot(
                        id = "slot_fruit",
                        name = "Borówki",
                        amount = 40.0,
                        unit = "g",
                        alternatives = listOf("Truskawki" to 50.0),
                    ),
                    slot(id = "slot_milk", name = "Mleko", amount = 200.0, unit = "ml"),
                ),
        )

    private fun slot(
        id: String,
        name: String,
        amount: Double,
        unit: String,
        alternatives: List<Pair<String, Double>> = emptyList(),
    ): RecipeIngredientSlotUi =
        RecipeIngredientSlotUi(
            default =
                RecipeIngredientOptionUi(
                    id = "$id:default",
                    name = name,
                    amount = amount,
                    unit = unit,
                ),
            alternatives =
                alternatives.map { (altName, altAmount) ->
                    RecipeIngredientOptionUi(
                        id = "$id:alt:$altName",
                        name = altName,
                        amount = altAmount,
                        unit = unit,
                    )
                },
            id = id,
        )

    private fun addable(
        id: String,
        name: String,
    ): AddableIngredientUi =
        AddableIngredientUi(
            ingredientId = id,
            name = name,
            defaultAmount = 10.0,
            defaultUnit = "g",
        )
}
