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.InitWorkspaceRequest;
import p.packer.messages.PackerProjectContext;
import p.packer.models.PackerRegistryEntry;
import p.packer.models.PackerRegistryState;
import p.packer.repositories.FileSystemPackerRegistryRepository;

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

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

final class PackerWorkspaceFoundationTest {
    @TempDir
    Path tempDir;

    @Test
    void initWorkspaceCreatesAssetsControlStructureAndRegistry() throws Exception {
        final var mapper = new ObjectMapper();
        final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
        final PackerProjectContext project = project(tempDir.resolve("main"));

        final var result = foundation.initWorkspace(new InitWorkspaceRequest(project));

        assertEquals(Path.of("assets/.prometeu/index.json"), project.rootPath().relativize(result.registryPath()));
        assertTrue(Files.isDirectory(project.rootPath().resolve("assets")));
        assertTrue(Files.isDirectory(project.rootPath().resolve("assets/.prometeu")));
        assertTrue(Files.isRegularFile(project.rootPath().resolve("assets/.prometeu/index.json")));
        final String registryJson = Files.readString(project.rootPath().resolve("assets/.prometeu/index.json"));
        assertTrue(registryJson.contains("\"schema_version\""));
        assertTrue(registryJson.contains("\"next_asset_id\""));
    }

    @Test
    void registryRoundTripPreservesAllocatorAndEntries() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        final var mapper = new ObjectMapper();
        final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
        final PackerProjectContext project = project(projectRoot);
        foundation.initWorkspace(new InitWorkspaceRequest(project));

        final PackerRegistryState state = new PackerRegistryState(
                1,
                3,
                java.util.List.of(
                        new PackerRegistryEntry(1, "uuid-1", "ui/atlas"),
                        new PackerRegistryEntry(2, "uuid-2", "audio/ui_sounds")));
        foundation.saveRegistry(project, state);

        final PackerRegistryState reloaded = foundation.loadRegistry(project);

        assertEquals(1, reloaded.schemaVersion());
        assertEquals(3, reloaded.nextAssetId());
        assertEquals(2, reloaded.assets().size());
        assertEquals("audio/ui_sounds", reloaded.assets().get(1).root());
    }

    @Test
    void allocatorIsMonotonicAndPersistedAcrossSaveLoad() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        final var mapper = new ObjectMapper();
        final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
        final PackerProjectContext project = project(projectRoot);
        foundation.initWorkspace(new InitWorkspaceRequest(project));
        Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));

        final PackerRegistryEntry entry = foundation.allocateIdentity(project, foundation.loadRegistry(project), projectRoot.resolve("assets/ui/atlas"));
        final PackerRegistryState updated = foundation.appendAllocatedEntry(foundation.loadRegistry(project), entry);
        foundation.saveRegistry(project, updated);

        final PackerRegistryState reloaded = foundation.loadRegistry(project);

        assertEquals(2, reloaded.nextAssetId());
        assertEquals(1, reloaded.assets().size());
        assertEquals(1, reloaded.assets().getFirst().assetId());
        assertEquals("ui/atlas", reloaded.assets().getFirst().root());
    }

    @Test
    void duplicateRootsFailClearly() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
        Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), """
                {
                  "schema_version": 1,
                  "next_asset_id": 3,
                  "assets": [
                    { "asset_id": 1, "asset_uuid": "uuid-1", "root": "ui/atlas" },
                    { "asset_id": 2, "asset_uuid": "uuid-2", "root": "ui/atlas" }
                  ]
                }
                """);
        final var mapper = new ObjectMapper();
        final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);

        final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));

        assertTrue(exception.getMessage().contains("Duplicate asset root"));
    }

    @Test
    void malformedRegistryFailsClearly() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
        Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), "{ nope ");
        final var mapper = new ObjectMapper();
        final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);

        final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));

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

    @Test
    void unsupportedRegistrySchemaFailsClearly() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
        Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), """
                {
                  "schema_version": 99,
                  "next_asset_id": 1,
                  "assets": []
                }
                """);
        final var mapper = new ObjectMapper();
        final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);

        final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));

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

    @Test
    void untrustedRegistryRootFailsClearly() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        Files.createDirectories(projectRoot.resolve("assets/.prometeu"));
        Files.writeString(projectRoot.resolve("assets/.prometeu/index.json"), """
                {
                  "schema_version": 1,
                  "next_asset_id": 2,
                  "assets": [
                    { "asset_id": 1, "asset_uuid": "uuid-1", "root": "../escape" }
                  ]
                }
                """);
        final var mapper = new ObjectMapper();
        final FileSystemPackerRegistryRepository repository = new FileSystemPackerRegistryRepository(mapper);

        final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> repository.load(project(projectRoot)));

        assertTrue(exception.getMessage().contains("trusted assets boundary"));
    }

    @Test
    void lookupResolvesByIdUuidAndRootAndFailsOnMissingRoot() throws Exception {
        final Path projectRoot = tempDir.resolve("main");
        final var mapper = new ObjectMapper();
        final PackerWorkspaceFoundation foundation = new PackerWorkspaceFoundation(mapper);
        final PackerProjectContext project = project(projectRoot);
        foundation.initWorkspace(new InitWorkspaceRequest(project));
        Files.createDirectories(projectRoot.resolve("assets/ui/atlas"));
        final PackerRegistryState state = new PackerRegistryState(
                1,
                2,
                java.util.List.of(new PackerRegistryEntry(1, "uuid-1", "ui/atlas")));

        final PackerRegistryLookup lookup = foundation.lookup();

        assertTrue(lookup.findByAssetId(state, 1).isPresent());
        assertTrue(lookup.findByAssetUuid(state, "uuid-1").isPresent());
        assertTrue(lookup.findByRoot(project, state, projectRoot.resolve("assets/ui/atlas")).isPresent());
        assertEquals(projectRoot.resolve("assets/ui/atlas").toAbsolutePath().normalize(), lookup.resolveExistingRoot(project, state.assets().getFirst()));

        Files.delete(projectRoot.resolve("assets/ui/atlas"));
        final p.packer.exceptions.PackerRegistryException exception = assertThrows(p.packer.exceptions.PackerRegistryException.class, () -> lookup.resolveExistingRoot(project, state.assets().getFirst()));
        assertTrue(exception.getMessage().contains("does not exist"));
    }

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