package p.packer.services;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import p.packer.messages.PackerOperationStatus;
import p.packer.messages.PackerProjectContext;
import p.packer.messages.AssetReference;
import p.packer.messages.assets.PackerBuildParticipation;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.assets.PackerAssetState;
import p.packer.messages.GetAssetDetailsRequest;
import p.packer.testing.PackerFixtureLocator;

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

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

final class PackerAssetDetailsServiceTest {
    @TempDir
    Path tempDir;

    @Test
    void returnsRegisteredDetailsForRegisteredAssetReferenceById() throws Exception {
        final Path projectRoot = copyFixture("workspaces/managed-basic", tempDir.resolve("managed"));
        final PackerAssetDetailsService service = service();

        final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forAssetId(1)));

        assertEquals(PackerOperationStatus.SUCCESS, result.status());
        assertEquals(PackerAssetState.REGISTERED, result.details().summary().state());
        assertEquals(PackerBuildParticipation.INCLUDED, result.details().summary().buildParticipation());
        assertEquals("fixture-uuid-1", result.details().summary().identity().assetUuid());
        assertEquals("ui_atlas", result.details().summary().identity().assetName());
        assertEquals("TILES/indexed_v1", result.details().outputFormat());
        assertEquals(List.of(OutputCodecCatalog.NONE), result.details().availableOutputCodecs());
        assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE));
        assertTrue(result.diagnostics().isEmpty());
    }

    @Test
    void returnsUnregisteredDetailsForValidUnregisteredRootReference() throws Exception {
        final Path projectRoot = copyFixture("workspaces/orphan-valid", tempDir.resolve("orphan"));
        final PackerAssetDetailsService service = service();

        final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forRelativeAssetRoot("orphans/ui_sounds")));

        assertEquals(PackerOperationStatus.SUCCESS, result.status());
        assertEquals(PackerAssetState.UNREGISTERED, result.details().summary().state());
        assertEquals(PackerBuildParticipation.EXCLUDED, result.details().summary().buildParticipation());
        assertEquals("orphan-uuid-1", result.details().summary().identity().assetUuid());
        assertEquals("ui_sounds", result.details().summary().identity().assetName());
        assertEquals(List.of(OutputCodecCatalog.NONE), result.details().availableOutputCodecs());
        assertEquals(List.of(), result.details().codecConfigurationFieldsByCodec().get(OutputCodecCatalog.NONE));
    }

    @Test
    void returnsInvalidDetailsForInvalidDeclaration() throws Exception {
        final Path projectRoot = copyFixture("workspaces/invalid-missing-fields", tempDir.resolve("invalid"));
        final PackerAssetDetailsService service = service();

        final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(projectRoot), AssetReference.forRelativeAssetRoot("bad")));

        assertEquals(PackerOperationStatus.FAILED, result.status());
        assertEquals(PackerAssetState.UNREGISTERED, result.details().summary().state());
        assertEquals(PackerBuildParticipation.EXCLUDED, result.details().summary().buildParticipation());
        assertFalse(result.diagnostics().isEmpty());
    }

    @Test
    void returnsFailureWhenReferenceCannotBeResolved() {
        final PackerAssetDetailsService service = service();

        final var result = service.getAssetDetails(new GetAssetDetailsRequest(project(tempDir.resolve("empty")), AssetReference.forRelativeAssetRoot("missing/root")));

        assertEquals(PackerOperationStatus.FAILED, result.status());
        assertEquals(PackerAssetState.UNREGISTERED, result.details().summary().state());
        assertEquals(PackerBuildParticipation.EXCLUDED, result.details().summary().buildParticipation());
        assertTrue(result.diagnostics().stream().anyMatch(diagnostic -> diagnostic.message().contains("could not be resolved")));
    }

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

    private PackerAssetDetailsService service() {
        final var foundation = new p.packer.services.PackerWorkspaceFoundation();
        final var parser = new PackerAssetDeclarationParser();
        final var runtimeRegistry = new PackerRuntimeRegistry(new PackerRuntimeLoader(foundation, parser));
        final var resolver = new PackerAssetReferenceResolver(foundation.lookup());
        return new PackerAssetDetailsService(runtimeRegistry, resolver);
    }

    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;
    }
}
