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

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 AssetDetailsColorSchemaCoordinatorTest {
    @Test
    void createFlowExcludesTilesAlreadyBoundToAnotherSchema() {
        final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
        final AssetWorkspaceBankCompositionFile tileA = tile("a.png", "artifact-a", "fp-a", List.of("#FF0000"));
        final AssetWorkspaceBankCompositionFile tileB = tile("b.png", "artifact-b", "fp-b", List.of("#00FF00"));
        coordinator.replaceDetails(details(
                List.of(tileA, tileB),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                Map.of(1, "#FF0000"),
                                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-a")))),
                        List.of(),
                        null)));

        assertEquals(List.of(tileB), coordinator.availableTilesForCreate());
    }

    @Test
    void createFlowListsSelectedTilesBeforePaletteMetadataExists() {
        final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
        final AssetWorkspaceBankCompositionFile tile = selectedArtifactOnly("a.png", "artifact-a", "fp-a");
        coordinator.replaceDetails(details(
                List.of(tile),
                new AssetStudioGlyphMaterialMetadata(List.of(), List.of(), null)));

        assertEquals(List.of(tile), coordinator.availableTilesForCreate());
        assertEquals(List.of(), AssetDetailsColorSchemaCoordinator.tileColors(tile));
    }

    @Test
    void createFlowUsesSelectedFingerprintWhenCurrentFileFingerprintIsMissing() {
        final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
        final AssetWorkspaceBankCompositionFile tile = new AssetWorkspaceBankCompositionFile(
                "a.png",
                "a.png",
                64L,
                1L,
                null,
                Map.of(
                        "artifactId", "artifact-a",
                        "selectedFingerprint", "fp-a"));
        coordinator.replaceDetails(details(
                List.of(tile),
                new AssetStudioGlyphMaterialMetadata(List.of(), List.of(), null)));

        assertEquals(List.of(tile), coordinator.availableTilesForCreate());
        final AssetStudioGlyphMaterialMetadata metadata = coordinator.upsertSchema(
                "schema-1",
                List.of(tile),
                List.of("#FF0000"));
        assertEquals("fp-a", metadata.schemas().getFirst().artifactBindings().getFirst().fingerprint());
    }

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

        final AssetStudioGlyphMaterialMetadata updated = coordinator.upsertSchema(
                "schema-1",
                List.of(tileA, tileB),
                List.of("#FF0000", "#00FF00"));

        final AssetStudioGlyphMaterialSchema schema = updated.schemas().getFirst();
        assertEquals(2, schema.version());
        assertEquals(Map.of(1, "#FF0000", 2, "#00FF00"), schema.materialIndices());
        assertEquals(List.of("artifact-a", "artifact-b"), schema.artifactBindings().stream().map(AssetStudioGlyphMaterialArtifactBinding::artifactId).toList());
    }

    @Test
    void removeIsBlockedWhenPalettesStillReferenceSchema() {
        final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
        coordinator.replaceDetails(details(
                List.of(tile("a.png", "artifact-a", "fp-a", List.of("#FF0000"))),
                new AssetStudioGlyphMaterialMetadata(
                        List.of(new AssetStudioGlyphMaterialSchema(
                                "schema-1",
                                AssetStudioGlyphMaterialSchemaStatus.OK,
                                1,
                                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)));

        assertFalse(coordinator.canRemove("schema-1"));
        assertThrows(IllegalStateException.class, () -> coordinator.removeSchema("schema-1"));
    }

    @Test
    void effectiveStatusBecomesInconsistentWhenArtifactFingerprintDrifts() {
        final AssetStudioGlyphMaterialSchema schema = new AssetStudioGlyphMaterialSchema(
                "schema-1",
                AssetStudioGlyphMaterialSchemaStatus.OK,
                1,
                Map.of(1, "#FF0000"),
                List.of(new AssetStudioGlyphMaterialArtifactBinding("artifact-a", "fp-old")));

        assertEquals(
                AssetStudioGlyphMaterialSchemaStatus.INCONSISTENT,
                AssetDetailsColorSchemaCoordinator.effectiveStatus(
                        schema,
                        List.of(tile("a.png", "artifact-a", "fp-new", List.of("#FF0000", "#FF0001")))));
    }

    @Test
    void schemaUpdateCanRefreshOnlyFingerprintAndIncrementVersion() {
        final AssetDetailsColorSchemaCoordinator coordinator = new AssetDetailsColorSchemaCoordinator();
        final AssetWorkspaceBankCompositionFile changedTile = tile("a.png", "artifact-a", "fp-new", List.of("#FF0000"));
        coordinator.replaceDetails(details(
                List.of(changedTile),
                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(),
                        null)));

        final AssetStudioGlyphMaterialSchema updated = coordinator.upsertSchema(
                "schema-1",
                List.of(changedTile),
                List.of("#FF0000")).schemas().getFirst();

        assertEquals(2, updated.version());
        assertEquals(AssetStudioGlyphMaterialSchemaStatus.OK, updated.status());
        assertEquals("fp-new", updated.artifactBindings().getFirst().fingerprint());
    }

    private AssetWorkspaceAssetDetails details(
            List<AssetWorkspaceBankCompositionFile> selectedTiles,
            AssetStudioGlyphMaterialMetadata metadata) {
        return new AssetWorkspaceAssetDetails(
                new AssetWorkspaceAssetSummary(
                        AssetReference.forAssetId(1),
                        "glyph_bank",
                        AssetWorkspaceAssetState.REGISTERED,
                        AssetWorkspaceBuildParticipation.INCLUDED,
                        1,
                        AssetFamilyCatalog.GLYPH_BANK,
                        AssetStudioGlyphSpecialization.SPRITES,
                        Path.of("/tmp/glyph"),
                        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(), selectedTiles, 0L),
                List.of(),
                metadata,
                null,
                AssetWorkspaceSceneBankValidation.NOT_APPLICABLE,
                List.of());
    }

    private AssetWorkspaceBankCompositionFile tile(String path, String artifactId, String fingerprint, List<String> colors) {
        final List<Integer> argb = colors.stream()
                .map(color -> Integer.parseUnsignedInt("FF" + color.substring(1), 16))
                .map(Integer::valueOf)
                .toList();
        return new AssetWorkspaceBankCompositionFile(
                path,
                path,
                64L,
                1L,
                fingerprint,
                Map.of(
                        "artifactId", artifactId,
                        "tile", Map.of("width", 8, "height", 8, "paletteIndices", List.of(1, 1, 1)),
                        "palette", Map.of("originalArgb8888", argb, "convertedRgb565", List.of())));
    }

    private AssetWorkspaceBankCompositionFile selectedArtifactOnly(String path, String artifactId, String fingerprint) {
        return new AssetWorkspaceBankCompositionFile(
                path,
                path,
                64L,
                1L,
                fingerprint,
                Map.of("artifactId", artifactId));
    }
}
