package p.studio.workspaces.assets.scene;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.dtos.PackerCodecConfigurationFieldDTO;
import p.packer.messages.AssetReference;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.assets.OutputFormatCatalog;
import p.packer.messages.assets.PackerCodecConfigurationFieldType;
import p.studio.projects.ProjectReference;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
import p.studio.workspaces.assets.metadata.AssetStudioSceneBankMetadata;
import p.studio.workspaces.assets.metadata.AssetStudioSceneLayerBinding;
import p.studio.workspaces.assets.metadata.AssetStudioSceneTile;
import p.studio.workspaces.assets.messages.*;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

final class SceneBankWorkflowServiceTest {
    private final ObjectMapper mapper = new ObjectMapper();
    private final SceneBankWorkflowService workflowService = new SceneBankWorkflowService((projectReference, tilesetAssetRoot) ->
            "tilesets/overworld".equals(tilesetAssetRoot) ? 1 : -1);

    @TempDir
    Path tempDir;

    @Test
    void inspectProjectsCanonicalSceneStateIntoReadyRequest() throws Exception {
        final ProjectReference project = projectReference();
        final AssetWorkspaceAssetDetails sceneDetails = createValidScene(project);

        final SceneBankWorkflowResult result = workflowService.inspect(project, sceneDetails);

        assertEquals(AssetWorkspaceSceneBankStatus.READY, result.validation().status());
        assertFalse(result.validation().pendingExternalChanges());
        assertFalse(result.validation().canAccept());
        assertTrue(Files.isRegularFile(sceneDetails.summary().assetRoot().resolve(SceneBankWorkflowService.ACCEPTED_REQUEST_FILE)));
    }

    @Test
    void validationFailsWhenReferencedGlyphAssetCannotResolveTileSize() throws Exception {
        final ProjectReference project = projectReference();
        final Path sceneRoot = project.rootPath().resolve("assets/scenes/broken");
        Files.createDirectories(sceneRoot);
        final AssetWorkspaceAssetDetails brokenScene = new AssetWorkspaceAssetDetails(
                new AssetWorkspaceAssetSummary(
                        AssetReference.forAssetId(2),
                        "broken_scene",
                        AssetWorkspaceAssetState.REGISTERED,
                        AssetWorkspaceBuildParticipation.EXCLUDED,
                        2,
                        AssetFamilyCatalog.SCENE_BANK,
                        AssetStudioGlyphSpecialization.NONE,
                        sceneRoot,
                        false,
                        false),
                List.of(),
                OutputFormatCatalog.SCENE_TILED_V1,
                OutputCodecCatalog.NONE,
                List.of(OutputCodecCatalog.NONE),
                Map.of(OutputCodecCatalog.NONE, List.of()),
                List.of(),
                Map.of(),
                new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L),
                List.of(),
                null,
                new AssetStudioSceneBankMetadata(
                        8,
                        8,
                        4,
                        List.of(
                                new AssetStudioSceneLayerBinding(true, 1, "Ground", 8, 8, "ground.tmx", "tilesets/missing", 1.0f, 1.0f, 0),
                                new AssetStudioSceneLayerBinding(false, 2, "Layer 2", 8, 8, "layer-2.tmx", "", 1.0f, 1.0f, 0),
                                new AssetStudioSceneLayerBinding(false, 3, "Layer 3", 8, 8, "layer-3.tmx", "", 1.0f, 1.0f, 0),
                                new AssetStudioSceneLayerBinding(false, 4, "Layer 4", 8, 8, "layer-4.tmx", "", 1.0f, 1.0f, 0)),
                        sceneRoot.resolve("scene-bank.studio.json")),
                AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
                List.of());

        final SceneBankWorkflowResult result = workflowService.inspect(project, brokenScene);

        assertEquals(AssetWorkspaceSceneBankStatus.VALIDATION_FAILED, result.validation().status());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("tile_size")));
    }

    @Test
    void inspectPersistsCanonicalScenePackRequestUsingCanonicalTiles() throws Exception {
        final ProjectReference project = projectReference();
        final AssetWorkspaceAssetDetails sceneDetails = createValidScene(project);

        final SceneBankWorkflowResult result = workflowService.inspect(project, sceneDetails);
        assertEquals(AssetWorkspaceSceneBankStatus.READY, result.validation().status());

        final JsonNode request = mapper.readTree(sceneDetails.summary().assetRoot().resolve(SceneBankWorkflowService.ACCEPTED_REQUEST_FILE).toFile());
        assertEquals("SCENE/tilemap_v1", request.path("format").asText());
        assertEquals(1, request.path("version").asInt());
        final JsonNode firstLayer = request.path("layers").get(0);
        assertTrue(firstLayer.path("active").asBoolean());
        assertEquals(1, firstLayer.path("glyph_asset_id").asInt());
        assertEquals(2, firstLayer.path("tiles").get(0).path("palette_id").asInt());
        assertEquals(7, firstLayer.path("tiles").get(0).path("glyph_id").asInt());
        assertEquals(3, firstLayer.path("tiles").get(1).path("palette_id").asInt());
        assertEquals(9, firstLayer.path("tiles").get(1).path("glyph_id").asInt());
        assertTrue(firstLayer.path("tiles").get(1).path("flip_x").asBoolean());
        final JsonNode secondLayer = request.path("layers").get(1);
        assertFalse(secondLayer.path("active").asBoolean());
        assertEquals(1, secondLayer.path("glyph_asset_id").asInt());
        assertEquals(16, secondLayer.path("tile_size").asInt());
    }

    private ProjectReference projectReference() {
        return new ProjectReference("main", "1", "pbs", 1, tempDir.resolve("project"));
    }

    private AssetWorkspaceAssetDetails createValidScene(ProjectReference project) throws Exception {
        final Path tilesetRoot = project.rootPath().resolve("assets/tilesets/overworld");
        Files.createDirectories(tilesetRoot);
        Files.writeString(tilesetRoot.resolve("a.png"), "fixture");
        Files.writeString(tilesetRoot.resolve("asset.json"), """
                {
                  "output": {
                    "metadata": {
                      "tile_size": "16x16"
                    }
                  }
                }
                """);

        final Path sceneRoot = project.rootPath().resolve("assets/scenes/overworld");
        Files.createDirectories(sceneRoot);
        return sceneDetails(sceneRoot);
    }

    private AssetWorkspaceAssetDetails sceneDetails(Path assetRoot) {
        return new AssetWorkspaceAssetDetails(
                new AssetWorkspaceAssetSummary(
                        AssetReference.forAssetId(2),
                        "overworld_scene",
                        AssetWorkspaceAssetState.REGISTERED,
                        AssetWorkspaceBuildParticipation.EXCLUDED,
                        2,
                        AssetFamilyCatalog.SCENE_BANK,
                        AssetStudioGlyphSpecialization.NONE,
                        assetRoot,
                        false,
                        false),
                List.of(),
                OutputFormatCatalog.SCENE_TILED_V1,
                OutputCodecCatalog.NONE,
                List.of(OutputCodecCatalog.NONE),
                Map.of(OutputCodecCatalog.NONE, List.of()),
                List.of(new PackerCodecConfigurationFieldDTO(
                        "tile_size",
                        "Tile Size",
                        PackerCodecConfigurationFieldType.ENUM,
                        "16x16",
                        true,
                        List.of("8x8", "16x16", "32x32"))),
                Map.of(),
                new AssetWorkspaceBankCompositionDetails(List.of(), List.of(), 0L),
                List.of(),
                null,
                new AssetStudioSceneBankMetadata(
                        16,
                        12,
                        4,
                        List.of(
                                new AssetStudioSceneLayerBinding(
                                        true,
                                        1,
                                        "Ground",
                                        16,
                                        12,
                                        "ground.tmx",
                                        "tilesets/overworld",
                                        1.0f,
                                        1.0f,
                                        2,
                                        canonicalTiles(16, 12)),
                                new AssetStudioSceneLayerBinding(false, 2, "Layer 2", 16, 12, "layer-2.tmx", "", 1.0f, 1.0f, 0),
                                new AssetStudioSceneLayerBinding(false, 3, "Layer 3", 16, 12, "layer-3.tmx", "", 1.0f, 1.0f, 0),
                                new AssetStudioSceneLayerBinding(false, 4, "Layer 4", 16, 12, "layer-4.tmx", "", 1.0f, 1.0f, 0)),
                        assetRoot.resolve("scene-bank.studio.json")),
                AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
                List.of());
    }

    private List<AssetStudioSceneTile> canonicalTiles(int width, int height) {
        final ArrayList<AssetStudioSceneTile> tiles = new ArrayList<>(width * height);
        tiles.add(new AssetStudioSceneTile(true, 7, 2, false, false));
        tiles.add(new AssetStudioSceneTile(true, 9, 3, true, false));
        while (tiles.size() < width * height) {
            tiles.add(AssetStudioSceneTile.inactive(2));
        }
        return List.copyOf(tiles);
    }
}
