package p.packer.repositories;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.PackerWorkspacePaths;
import p.packer.exceptions.PackerRegistryException;
import p.packer.messages.PackerProjectContext;
import p.packer.models.PackerAssetCacheEntry;
import p.packer.models.PackerDiagnostic;
import p.packer.models.PackerFileCacheEntry;
import p.packer.models.PackerWorkspaceCacheState;
import p.packer.messages.diagnostics.PackerDiagnosticCategory;
import p.packer.messages.diagnostics.PackerDiagnosticSeverity;

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

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

final class FileSystemPackerCacheRepositoryTest {
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @TempDir
    Path tempDir;

    @Test
    void returnsEmptyStateWhenCacheIsAbsent() {
        final var repository = new FileSystemPackerCacheRepository(MAPPER);

        final var state = repository.load(project(tempDir.resolve("project")));

        assertEquals(PackerWorkspaceCacheState.EMPTY, state);
    }

    @Test
    void savesAndLoadsCacheRoundTrip() throws Exception {
        final var repository = new FileSystemPackerCacheRepository(MAPPER);
        final var project = project(tempDir.resolve("project"));
        final var state = new PackerWorkspaceCacheState(
                PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION,
                List.of(
                        new PackerAssetCacheEntry(3, "contract-3", List.of(
                                new PackerFileCacheEntry(
                                        "tiles/ui.png",
                                        "image/png",
                                        128L,
                                        42L,
                                        "abc123",
                                        Map.of("tile_count", 4),
                                        List.of(new PackerDiagnostic(
                                                PackerDiagnosticSeverity.WARNING,
                                                PackerDiagnosticCategory.HYGIENE,
                                                "cached warning",
                                                Path.of("/tmp/ui.png"),
                                                false))),
                                new PackerFileCacheEntry(
                                        "tiles/ui-2.png",
                                        "image/png",
                                        256L,
                                        84L,
                                        "def456",
                                        Map.of(),
                                        List.of()))),
                        new PackerAssetCacheEntry(1, "contract-1", List.of(
                                new PackerFileCacheEntry(
                                        "audio/click.wav",
                                        "audio/wav",
                                        512L,
                                        21L,
                                        null,
                                        Map.of("sample_count", 8),
                                        List.of())))));

        repository.save(project, state);
        final var loaded = repository.load(project);

        assertEquals(List.of(1, 3), loaded.assets().stream().map(PackerAssetCacheEntry::assetId).toList());
        assertEquals("contract-3", loaded.findAsset(3).orElseThrow().contractFingerprint());
        assertTrue(loaded.findAsset(3).flatMap(asset -> asset.findFile("tiles/ui.png")).isPresent());
        assertEquals(128L, loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().size());
        assertEquals(1, loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().diagnostics().size());
        assertEquals("cached warning", loaded.findAsset(3).orElseThrow().findFile("tiles/ui.png").orElseThrow().diagnostics().getFirst().message());
    }

    @Test
    void rejectsMalformedCacheFile() throws Exception {
        final var repository = new FileSystemPackerCacheRepository(MAPPER);
        final var project = project(tempDir.resolve("project"));
        Files.createDirectories(PackerWorkspacePaths.registryDirectory(project));
        Files.writeString(PackerWorkspacePaths.cachePath(project), "{ nope ");

        final var exception = assertThrows(PackerRegistryException.class, () -> repository.load(project));

        assertTrue(exception.getMessage().contains("Unable to load cache"));
    }

    @Test
    void rejectsUnsupportedSchemaVersion() throws Exception {
        final var repository = new FileSystemPackerCacheRepository(MAPPER);
        final var project = project(tempDir.resolve("project"));
        Files.createDirectories(PackerWorkspacePaths.registryDirectory(project));
        Files.writeString(PackerWorkspacePaths.cachePath(project), """
                {
                  "schema_version": 999,
                  "assets": []
                }
                """);

        final var exception = assertThrows(PackerRegistryException.class, () -> repository.load(project));

        assertTrue(exception.getMessage().contains("Unsupported cache schema_version"));
    }

    @Test
    void alignsLookupByAssetId() {
        final var state = new PackerWorkspaceCacheState(
                PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION,
                List.of(
                        new PackerAssetCacheEntry(7, "contract-7", List.of()),
                        new PackerAssetCacheEntry(9, "contract-9", List.of())));

        assertTrue(state.findAsset(7).isPresent());
        assertTrue(state.findAsset(8).isEmpty());
    }

    @Test
    void serializesDiagnosticsWhenPresent() throws Exception {
        final var repository = new FileSystemPackerCacheRepository(MAPPER);
        final var project = project(tempDir.resolve("project"));
        final var state = new PackerWorkspaceCacheState(
                PackerWorkspaceCacheState.CURRENT_SCHEMA_VERSION,
                List.of(new PackerAssetCacheEntry(1, "contract-1", List.of(
                        new PackerFileCacheEntry(
                                "tiles/ui.png",
                                "image/png",
                                128L,
                                42L,
                                "abc123",
                                Map.of("warning_count", 2),
                                List.of(new PackerDiagnostic(
                                        PackerDiagnosticSeverity.WARNING,
                                        PackerDiagnosticCategory.HYGIENE,
                                        "cached diagnostic",
                                        Path.of("/tmp/ui.png"),
                                        false)))))));

        repository.save(project, state);
        final String json = Files.readString(PackerWorkspacePaths.cachePath(project));

        assertTrue(json.contains("\"diagnostics\""));
        assertTrue(json.contains("\"cached diagnostic\""));
        assertTrue(json.contains("\"asset_id\""));
        assertTrue(json.contains("\"contract_fingerprint\""));
        assertTrue(json.contains("\"metadata\""));
    }

    private PackerProjectContext project(Path rootPath) {
        return new PackerProjectContext("main", rootPath);
    }
}
