package p.packer.services;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
import p.packer.testing.PackerFixtureLocator;

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

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

final class PackerAssetDeclarationParserTest {
    private final PackerAssetDeclarationParser parser = new PackerAssetDeclarationParser(new ObjectMapper());

    @TempDir
    Path tempDir;

    @Test
    void parsesValidDeclarationFixture() {
        final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/managed-basic/assets/ui/atlas/asset.json"));

        assertTrue(result.valid());
        assertNotNull(result.declaration());
        assertEquals(1, result.declaration().schemaVersion());
        assertEquals("fixture-uuid-1", result.declaration().assetUuid());
        assertEquals("ui_atlas", result.declaration().name());
        assertEquals(AssetFamilyCatalog.GLYPH_BANK, result.declaration().assetFamily());
        assertEquals("GLYPH/indexed_v1", result.declaration().outputFormat().displayName());
        assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec());
        assertEquals(Map.of(), result.declaration().outputPipelineMetadata());
        assertTrue(result.declaration().preloadEnabled());
    }

    @Test
    void parsesPipelineMetadataAsDedicatedOutputObject() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-pipeline",
                  "name": "pipeline_asset",
                  "type": "sound_bank",
                  "output": {
                    "format": "SOUND/v1",
                    "codec": "NONE",
                    "pipeline": {
                      "samples": {
                        "1": { "offset": 0, "length": 128 }
                      },
                      "normalized": true
                    }
                  },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertTrue(result.valid());
        assertEquals(2, result.declaration().outputPipelineMetadata().size());
        assertTrue(result.declaration().outputPipelineMetadata().get("normalized").asBoolean());
        assertEquals(128, result.declaration().outputPipelineMetadata().get("samples").path("1").path("length").asInt());
    }

    @Test
    void parsesSceneBankDeclaration() throws Exception {
        final Path manifest = tempDir.resolve("scene-asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-scene",
                  "name": "overworld_scene",
                  "type": "scene_bank",
                  "output": {
                    "format": "SCENE/tilemap_v1",
                    "codec": "NONE"
                  },
                  "preload": { "enabled": false }
                }
                """);

        final var result = parser.parse(manifest);

        assertTrue(result.valid());
        assertEquals(AssetFamilyCatalog.SCENE_BANK, result.declaration().assetFamily());
        assertEquals("SCENE/tilemap_v1", result.declaration().outputFormat().displayName());
        assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec());
    }

    @Test
    void parsesLegacySceneBankDeclarationFormatAsTilemapContract() throws Exception {
        final Path manifest = tempDir.resolve("legacy-scene-asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-scene-legacy",
                  "name": "overworld_scene",
                  "type": "scene_bank",
                  "output": {
                    "format": "SCENE/tiled_v1",
                    "codec": "NONE"
                  },
                  "preload": { "enabled": false }
                }
                """);

        final var result = parser.parse(manifest);

        assertTrue(result.valid());
        assertEquals(AssetFamilyCatalog.SCENE_BANK, result.declaration().assetFamily());
        assertEquals("SCENE/tilemap_v1", result.declaration().outputFormat().displayName());
        assertEquals(OutputCodecCatalog.NONE, result.declaration().outputCodec());
    }

    @Test
    void rejectsNonObjectPipelineMetadata() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-pipeline",
                  "name": "pipeline_asset",
                  "type": "glyph_bank",
                  "output": {
                    "format": "GLYPH/indexed_v1",
                    "codec": "NONE",
                    "pipeline": []
                  },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("output.pipeline")));
    }

    @Test
    void parsesPaletteDeclarationsUsingExplicitIndexContract() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-palette-contract",
                  "name": "palette_asset",
                  "type": "glyph_bank",
                  "output": {
                    "format": "GLYPH/indexed_v1",
                    "codec": "NONE",
                    "pipeline": {
                      "palettes": [
                        {
                          "index": 1,
                          "palette": {
                            "originalArgb8888": [4294901760],
                            "convertedRgb565": [63488]
                          }
                        }
                      ]
                    }
                  },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertTrue(result.valid());
        assertTrue(result.declaration().outputPipelineMetadata().get("palettes").isArray());
        assertEquals(1, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("index").asInt());
        assertEquals(63488, result.declaration().outputPipelineMetadata().get("palettes").get(0).path("palette").path("convertedRgb565").get(0).asInt());
    }

    @Test
    void rejectsPaletteDeclarationsWithDuplicateIndices() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-palette-duplicate",
                  "name": "palette_asset",
                  "type": "glyph_bank",
                  "output": {
                    "format": "GLYPH/indexed_v1",
                    "codec": "NONE",
                    "pipeline": {
                      "palettes": [
                        {
                          "index": 0,
                          "palette": {
                            "originalArgb8888": [4294901760],
                            "convertedRgb565": [63488]
                          }
                        },
                        {
                          "index": 0,
                          "palette": {
                            "originalArgb8888": [4278255360],
                            "convertedRgb565": [2016]
                          }
                        }
                      ]
                    }
                  },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("duplicate palette index 0")));
    }

    @Test
    void rejectsMalformedJsonWithStructuralDiagnostic() {
        final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-malformed/assets/bad/asset.json"));

        assertFalse(result.valid());
        assertNull(result.declaration());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.category() == PackerDiagnosticCategory.STRUCTURAL));
    }

    @Test
    void rejectsMissingRequiredFields() {
        final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-missing-fields/assets/bad/asset.json"));

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("name")));
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("asset_uuid")));
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("format")));
    }

    @Test
    void rejectsUnsupportedSchemaVersion() {
        final var result = parser.parse(PackerFixtureLocator.fixtureRoot("workspaces/invalid-version/assets/bad/asset.json"));

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.category() == PackerDiagnosticCategory.VERSIONING));
    }

    @Test
    void rejectsLegacyInputsField() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-outside",
                  "name": "bad_asset",
                  "type": "glyph_bank",
                  "inputs": { "sprites": ["../outside.png"] },
                  "output": { "format": "GLYPH/indexed_v1", "codec": "NONE" },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("no longer supported")));
    }

    @Test
    void rejectsUnknownAssetFamily() throws Exception {
        final Path manifest = tempDir.resolve("asset.json");
        Files.writeString(manifest, """
                {
                  "schema_version": 1,
                  "asset_uuid": "uuid-video",
                  "name": "bad_asset",
                  "type": "video_bank",
                  "output": { "format": "GLYPH/indexed_v1", "codec": "NONE" },
                  "preload": { "enabled": true }
                }
                """);

        final var result = parser.parse(manifest);

        assertFalse(result.valid());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("Field 'type' must be one of")));
    }
}
