package p.studio.workspaces.assets.details.palette;

import org.junit.jupiter.api.Test;
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.studio.workspaces.assets.metadata.*;
import p.studio.workspaces.assets.messages.*;

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

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

final class AssetDetailsPaletteOverhaulingCoordinatorTest {
    @Test
    void derivesOriginalPaletteFromReadySchemaWhenNoPaletteRecordExists() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(),
                        null),
                List.of()));

        assertTrue(coordinator.ready());
        assertEquals(1, coordinator.viewModel().allFiles().size());
        assertEquals("schema-1 / original", coordinator.viewModel().allFiles().getFirst().displayName());
    }

    @Test
    void initialSelectionStillMatchesBuildFacingPipelinePalettePayload() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        final AssetStudioGlyphMaterialPalette persisted = new AssetStudioGlyphMaterialPalette(
                "palette-1",
                "schema-1",
                1,
                List.of("#FF0000", "#00FF00"));
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(persisted),
                        null),
                List.of(Map.of(
                        "originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00),
                        "convertedRgb565", List.of(0xF800, 0x07E0)))));

        assertEquals(1, coordinator.viewModel().selectedFiles().size());
        assertEquals("palette-1", coordinator.viewModel().selectedFiles().getFirst().metadata().get("paletteId"));
    }

    @Test
    void previewPaletteDefaultsToAvailablePaletteWhenBuildSelectionIsEmpty() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#112233", "#445566"))),
                        null),
                List.of()));

        assertNotNull(coordinator.viewModel().previewPaletteFile());
        assertTrue(coordinator.viewModel().availableFiles().contains(coordinator.viewModel().previewPaletteFile()));
    }

    @Test
    void originalPaletteRemainsSyntheticEvenWhenUserPaletteHasSameColors() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#FF0000", "#00FF00"))),
                        null),
                List.of()));

        assertEquals(2, coordinator.viewModel().allFiles().size());
        final AssetWorkspaceBankCompositionFile original = coordinator.viewModel().allFiles().stream()
                .filter(AssetDetailsPaletteOverhaulingCoordinator::paletteIsOriginal)
                .findFirst()
                .orElseThrow();
        final AssetWorkspaceBankCompositionFile userPalette = coordinator.viewModel().allFiles().stream()
                .filter(file -> "palette-1".equals(file.metadata().get("paletteId")))
                .findFirst()
                .orElseThrow();

        assertEquals("schema-schema-1-original", original.metadata().get("paletteId"));
        assertFalse(AssetDetailsPaletteOverhaulingCoordinator.paletteIsOriginal(userPalette));
    }

    @Test
    void originalPaletteTracksSchemaMaterialColorsAfterMetadataUpdate() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(),
                        null),
                List.of()));

        coordinator.replaceMaterialMetadata(new AssetStudioGlyphMaterialMetadata(
                List.of(new AssetStudioGlyphMaterialSchema(
                        "schema-1",
                        AssetStudioGlyphMaterialSchemaStatus.OK,
                        2,
                        Map.of(1, "#112233", 2, "#445566"),
                        List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                List.of(),
                null));

        final AssetWorkspaceBankCompositionFile original = coordinator.viewModel().allFiles().stream()
                .filter(AssetDetailsPaletteOverhaulingCoordinator::paletteIsOriginal)
                .findFirst()
                .orElseThrow();

        @SuppressWarnings("unchecked")
        final Map<String, Object> palette = (Map<String, Object>) original.metadata().get("palette");
        assertEquals(List.of(0xFF112233, 0xFF445566), palette.get("originalArgb8888"));
    }

    @Test
    void duplicateDetectionUsesSameSchemaColorsAndOrder() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#FF0000", "#00FF00"))),
                        null),
                List.of()));

        assertTrue(coordinator.duplicatePaletteExists(null, "schema-1", List.of("#FF0000", "#00FF00")));
        assertFalse(coordinator.duplicatePaletteExists(null, "schema-1", List.of("#00FF00", "#FF0000")));
    }

    @Test
    void paletteDisplayNameUsesExplicitNameWhenPresent() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "Forest",
                                "schema-1",
                                1,
                                List.of("#FF0000", "#00FF00"))),
                        null),
                List.of()));

        final AssetWorkspaceBankCompositionFile palette = coordinator.viewModel().allFiles().getFirst();
        assertEquals("Forest", palette.displayName());
        assertEquals("Forest", palette.metadata().get("explicitPaletteDisplayName"));
    }

    @Test
    void paletteDisplayNameFallsBackToColorsWhenNameIsBlank() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#FF0000", "#00FF00"))),
                        null),
                List.of()));

        final AssetWorkspaceBankCompositionFile palette = coordinator.viewModel().allFiles().getFirst();
        assertEquals("ember bloom", palette.displayName());
        assertFalse(palette.metadata().containsKey("explicitPaletteDisplayName"));
    }

    @Test
    void generatedPaletteDisplayNameUsesPoeticColorWords() {
        assertEquals("snow white", AssetDetailsPaletteOverhaulingCoordinator.generatedDisplayName(List.of("#FFFFFF")));
        assertEquals("fog twist", AssetDetailsPaletteOverhaulingCoordinator.generatedDisplayName(List.of("#F2F4F4", "#707878")));
    }

    @Test
    void upsertPalettePersistsGeneratedDisplayNameWhenNameIsBlank() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#F2F4F4", 2, "#707878"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(),
                        null),
                List.of()));

        final AssetStudioGlyphMaterialMetadata metadata = coordinator.upsertPalette(
                "palette-1",
                " ",
                "schema-1",
                List.of("#F2F4F4", "#707878"));

        assertEquals("fog twist", metadata.palettes().getFirst().displayName());
    }

    @Test
    void removePaletteDeletesUserPaletteEvenWhenSelectedForBuild() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#112233", "#445566"))),
                        null),
                List.of(Map.of(
                        "originalArgb8888", List.of(0xFF112233, 0xFF445566),
                        "convertedRgb565", List.of(0x1106, 0x42AC)))));

        final AssetStudioGlyphMaterialMetadata metadata = coordinator.removePalette("palette-1");

        assertTrue(metadata.palettes().isEmpty());
    }

    @Test
    void removePaletteRejectsSyntheticOriginalPalette() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000", 2, "#00FF00"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(),
                        null),
                List.of()));

        assertThrows(IllegalStateException.class, () -> coordinator.removePalette("schema-schema-1-original"));
    }

    @Test
    void stalePaletteIsProjectedAsInvalid() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                2,
                                Map.of(1, "#FF0000"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#FF0000"))),
                        null),
                List.of()));

        final AssetWorkspaceBankCompositionFile palette = coordinator.viewModel().allFiles().getFirst();
        assertEquals(Boolean.FALSE, palette.metadata().get("valid"));
        assertTrue(((String) palette.metadata().get("invalidReason")).contains("stale"));
    }

    @Test
    void paletteIsInvalidWhenSchemaArtifactFingerprintDrifts() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-old")))),
                        List.of(new AssetStudioGlyphMaterialPalette(
                                "palette-1",
                                "schema-1",
                                1,
                                List.of("#FF0000"))),
                        null),
                List.of()));

        final AssetWorkspaceBankCompositionFile palette = coordinator.viewModel().allFiles().getFirst();
        assertEquals(Boolean.FALSE, palette.metadata().get("valid"));
        assertTrue(((String) palette.metadata().get("invalidReason")).contains("inconsistent"));
    }


    @Test
    void previewTilesAreScopedToSelectedPaletteSchema() {
        final AssetDetailsPaletteOverhaulingCoordinator coordinator = new AssetDetailsPaletteOverhaulingCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("tile-a.png", "artifact-a"), tile("tile-b.png", "artifact-b")),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(
                                new AssetStudioGlyphMaterialSchema(
                                        "schema-1",
                                        AssetStudioGlyphMaterialSchemaStatus.OK,
                                        1,
                                        Map.of(1, "#FF0000", 2, "#00FF00"),
                                        List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a"))),
                                new AssetStudioGlyphMaterialSchema(
                                        "schema-2",
                                        AssetStudioGlyphMaterialSchemaStatus.OK,
                                        1,
                                        Map.of(1, "#0000FF"),
                                        List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-b", "fp-b")))),
                        List.of(),
                        null),
                List.of()));

        final AssetWorkspaceBankCompositionFile schema2Palette = coordinator.viewModel().allFiles().stream()
                .filter(file -> "schema-2".equals(file.metadata().get("schemaId")))
                .findFirst()
                .orElseThrow();
        coordinator.selectPreviewPalette(schema2Palette);

        assertEquals(List.of("tile-b.png"), coordinator.viewModel().previewTileFiles().stream().map(AssetWorkspaceBankCompositionFile::path).toList());
        assertEquals("schema-2", coordinator.viewModel().previewPaletteFile().metadata().get("schemaId"));
        assertEquals(Map.of(1, "#0000FF"), coordinator.viewModel().previewPaletteFile().metadata().get("schemaMaterialIndices"));
    }

    private AssetWorkspaceAssetDetails details(
            List<AssetWorkspaceBankCompositionFile> selectedFiles,
            AssetStudioGlyphMaterialMetadata metadata,
            List<Map<String, Object>> pipelinePalettes) {
        return new AssetWorkspaceAssetDetails(
                new AssetWorkspaceAssetSummary(
                        AssetReference.forAssetId(1),
                        "bank",
                        AssetWorkspaceAssetState.REGISTERED,
                        AssetWorkspaceBuildParticipation.INCLUDED,
                        1,
                        AssetFamilyCatalog.GLYPH_BANK,
                        AssetStudioGlyphSpecialization.SPRITES,
                        Path.of("/tmp/bank"),
                        false,
                        false),
                List.of(),
                OutputFormatCatalog.GLYPH_INDEXED_V1,
                OutputCodecCatalog.NONE,
                List.of(OutputCodecCatalog.NONE),
                Map.of(OutputCodecCatalog.NONE, List.of()),
                List.of(),
                Map.of(),
                new AssetWorkspaceBankCompositionDetails(List.of(), selectedFiles, 0L),
                pipelinePalettes,
                metadata,
                null,
                AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
                List.of());
    }

    private AssetWorkspaceBankCompositionFile tile(String path, String artifactId) {
        return new AssetWorkspaceBankCompositionFile(
                path,
                path,
                256L,
                1L,
                "artifact-a".equals(artifactId) ? "fp-a" : "fp-b",
                Map.of(
                        "artifactId", artifactId,
                        "tile", Map.of(
                                "width", 2,
                                "height", 2,
                                "paletteIndices", List.of(0, 1, 2, 0)),
                        "palette", Map.of(
                                "originalArgb8888", List.of(0xFFFF0000, 0xFF00FF00),
                                "convertedRgb565", List.of(0xF800, 0x07E0))));
    }
}
