// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0

package dan200.computercraft.gametest

import com.mojang.authlib.GameProfile
import dan200.computercraft.api.pocket.IPocketUpgrade
import dan200.computercraft.api.turtle.ITurtleUpgrade
import dan200.computercraft.gametest.api.*
import dan200.computercraft.impl.PocketUpgrades
import dan200.computercraft.impl.TurtleUpgrades
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.integration.UpgradeRecipeGenerator
import dan200.computercraft.shared.recipe.CustomShapedRecipe
import dan200.computercraft.shared.recipe.CustomShapelessRecipe
import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.component.DataComponents
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.ItemStackTemplate
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.junit.jupiter.api.Assertions.assertEquals
import java.util.*

class Recipe_Test {
    /**
     * Test that crafting results contain NBT data.
     *
     * Mostly useful for Fabric, where we need a mixin for this.
     */
    @GameTest(template = Structures.DEFAULT)
    fun Craft_result_has_nbt(context: GameTestHelper) = context.immediate {
        val result = context.craftItem(
            ItemStack(Items.SKELETON_SKULL),
            ItemStack(ModRegistry.Items.COMPUTER_ADVANCED.get()),
        )

        val profile = GameProfile(UUID.fromString("f3c8d69b-0776-4512-8434-d1b2165909eb"), "dan200")

        val tag =
            DataComponentPatch.builder().set(DataComponents.PROFILE, ResolvableProfile.createResolved(profile))
                .build()
        assertEquals(tag, result.componentsPatch, "Expected NBT tags to be the same")
    }

    /**
     * Test that all impostor recipes are craftable.
     */
    @GameTest(template = Structures.DEFAULT)
    fun Imposter_recipes_craftable(context: GameTestHelper) = context.immediate {
        for (holder in context.level.server.recipeManager.recipes) {
            val recipe = holder.value
            if (recipe is CustomShapedRecipe || recipe is CustomShapelessRecipe) {
                context.assertCraftable(recipe)
            }
        }
    }

    /**
     * Test that recipes generated by [UpgradeRecipeGenerator] are craftable.
     */
    @GameTest(template = Structures.DEFAULT)
    fun Upgrade_recipes_craftable(context: GameTestHelper) = context.immediate {
        val registries = context.level.registryAccess()
        val turtleUpgrades = registries.lookupOrThrow(ITurtleUpgrade.REGISTRY)
        val pocketUpgrades = registries.lookupOrThrow(IPocketUpgrade.REGISTRY)

        val generator = UpgradeRecipeGenerator(::BasicRecipe, registries)

        fun checkUsage(message: String, size: Int, item: ItemStackTemplate) {
            val recipes = generator.findRecipesWithInput(item.create())
            assertEquals(size, recipes.size, message)

            for (recipe in recipes) {
                // Ensure the ingredient appears in the recipe.
                assertThat("Input is in ingredients", recipe.ingredients, Matchers.hasItem(item))

                // Ensure the recipe can actually be crafted using the main recipe manager.
                context.assertCraftable(recipe)
            }
        }

        // Speakers should be an input to 4 recipes (2xPocket, 2xTurtle).
        checkUsage("Usage: Speaker", 4, ItemStackTemplate(ModRegistry.Items.SPEAKER.get()))

        // Check recipes which can be crafted with a turtle
        checkUsage(
            "Usage: Turtle without upgrades",
            (turtleUpgrades.listElements().count() * 2).toInt(),
            ItemStackTemplate(ModRegistry.Items.TURTLE_NORMAL.get()),
        )
        checkUsage(
            "Usage: Turtle with one upgrade",
            turtleUpgrades.listElements().count().toInt(),
            ItemStackTemplate(
                ModRegistry.Items.TURTLE_NORMAL.get(),
                DataComponentPatch.builder()
                    .set(
                        ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(),
                        TurtleUpgrades.instance().get(registries, ItemStack(ModRegistry.Items.SPEAKER.get()))!!,
                    )
                    .build(),
            ),
        )

        // Check recipes which can be crafted with a pocket computer
        checkUsage(
            "Usage: Pocket computer without upgrades",
            (pocketUpgrades.listElements().count() * 2).toInt(),
            ItemStackTemplate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()),
        )

        fun checkRecipe(message: String, size: Int, item: ItemStackTemplate) {
            val recipes = generator.findRecipesWithOutput(item.create())
            assertEquals(size, recipes.size, message)

            for (recipe in recipes) {
                // Ensure the result matches the input.
                assertEquals(item, recipe.result, "Recipe has output")

                // Ensure the recipe can actually be crafted using the main recipe manager.
                context.assertCraftable(recipe)
            }
        }

        // Check recipes which craft a turtle
        checkRecipe(
            "Recipe: Turtle with one upgrade",
            1,
            ItemStackTemplate(
                ModRegistry.Items.TURTLE_NORMAL.get(),
                DataComponentPatch.builder().set(
                    ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(),
                    TurtleUpgrades.instance().get(registries, ItemStack(ModRegistry.Items.SPEAKER.get()))!!,
                ).build(),
            ),
        )
        checkRecipe(
            "Recipe: Turtle with two upgrades",
            2,
            ItemStackTemplate(
                ModRegistry.Items.TURTLE_NORMAL.get(),
                DataComponentPatch.builder()
                    .set(
                        ModRegistry.DataComponents.LEFT_TURTLE_UPGRADE.get(),
                        TurtleUpgrades.instance()
                            .get(registries, ItemStack(ModRegistry.Items.SPEAKER.get()))!!,
                    )
                    .set(
                        ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(),
                        TurtleUpgrades.instance()
                            .get(registries, ItemStack(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()))!!,
                    )
                    .build(),
            ),
        )

        // Check recipes which craft a pocket computer
        checkRecipe(
            "Recipe: Pocket computer with one upgrade",
            1,
            ItemStackTemplate(
                ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(),
                DataComponentPatch.builder()
                    .set(
                        ModRegistry.DataComponents.BACK_POCKET_UPGRADE.get(),
                        PocketUpgrades.instance().get(registries, ItemStack(ModRegistry.Items.SPEAKER.get()))!!,
                    ).build(),
            ),
        )
    }
}

private class BasicRecipe(val width: Int, val height: Int, val ingredients: List<ItemStackTemplate>, val result: ItemStackTemplate)

private fun GameTestHelper.assertCraftable(recipe: BasicRecipe) {
    assertCraftable(
        recipe.ingredients.map(ItemStackTemplate::create),
        recipe.result.create(),
        width = recipe.width,
        height = recipe.height,
    )
}
