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.PackerProjectContext;
import p.packer.testing.PackerFixtureLocator;

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

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

final class PackerRuntimeRegistryTest {
    @TempDir
    Path tempDir;

    @Test
    void bootstrapsOneRuntimePerProjectAndReusesItUntilRefresh() throws Exception {
        final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed"));
        final PackerRuntimeRegistry registry = runtimeRegistry();
        final PackerProjectContext project = project(projectRoot);

        final PackerProjectRuntime first = registry.getOrLoad(project);
        final PackerProjectRuntime second = registry.getOrLoad(project);

        assertSame(first, second);
        assertEquals(1L, first.snapshot().generation());
        assertEquals(1, first.snapshot().registry().assets().size());
        assertEquals(1, first.snapshot().assets().size());
    }

    @Test
    void refreshRebuildsSnapshotWithoutReplacingRuntimeInstance() throws Exception {
        final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("refresh"));
        final PackerRuntimeRegistry registry = runtimeRegistry();
        final PackerProjectContext project = project(projectRoot);
        final PackerProjectRuntime runtime = registry.getOrLoad(project);

        Files.createDirectories(projectRoot.resolve("assets/audio/ui_sounds"));
        Files.writeString(projectRoot.resolve("assets/audio/ui_sounds/asset.json"), """
                {
                  "schema_version": 1,
                  "asset_uuid": "runtime-refresh-uuid",
                  "name": "ui_sounds",
                  "type": "audio.bank",
                  "inputs": {},
                  "output": {
                    "format": "SOUND/bank_v1",
                    "codec": "none"
                  },
                  "preload": {
                    "enabled": false
                  }
                }
                """);

        final PackerProjectRuntime refreshed = registry.refresh(project);

        assertSame(runtime, refreshed);
        assertTrue(refreshed.snapshot().generation() > 1L);
        assertEquals(2, refreshed.snapshot().assets().size());
    }

    @Test
    void disposeMarksRuntimeInactiveAndRemovesItFromRegistry() throws Exception {
        final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("dispose"));
        final PackerRuntimeRegistry registry = runtimeRegistry();
        final PackerProjectContext project = project(projectRoot);
        final PackerProjectRuntime runtime = registry.getOrLoad(project);

        registry.dispose(project);

        assertTrue(runtime.disposed());
        assertTrue(registry.find(project).isEmpty());
        assertThrows(IllegalStateException.class, runtime::snapshot);
    }

    private PackerRuntimeRegistry runtimeRegistry() {
        final var foundation = new PackerWorkspaceFoundation();
        final var parser = new PackerAssetDeclarationParser(new ObjectMapper());
        return new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
    }

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

    private Path copyFixture(String relativePath, Path targetRoot) throws Exception {
        final Path sourceRoot = PackerFixtureLocator.fixtureRoot(relativePath);
        try (var stream = Files.walk(sourceRoot)) {
            for (Path source : stream.sorted(Comparator.naturalOrder()).toList()) {
                final Path target = targetRoot.resolve(sourceRoot.relativize(source).toString());
                if (Files.isDirectory(source)) {
                    Files.createDirectories(target);
                } else {
                    Files.createDirectories(target.getParent());
                    Files.copy(source, target);
                }
            }
        }
        return targetRoot;
    }
}
