package p.studio.compiler.pbs;

import org.junit.jupiter.api.Test;
import p.studio.compiler.source.diagnostics.DiagnosticSink;
import p.studio.compiler.source.identifiers.FileId;

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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class PbsGoldenArtifactsTest {

    @Test
    void frontendIrBackendGoldenMustRemainDeterministic() throws IOException {
        final var source = """
                fn b(x: int) -> int {
                  return x;
                }

                fn a() -> int {
                  return b(1);
                }
                """;
        final var compiler = new PbsFrontendCompiler();
        final var diagnosticsA = DiagnosticSink.empty();
        final var diagnosticsB = DiagnosticSink.empty();
        final var first = compiler.compileFile(new FileId(1), source, diagnosticsA);
        final var second = compiler.compileFile(new FileId(1), source, diagnosticsB);

        assertTrue(diagnosticsA.isEmpty());
        assertTrue(diagnosticsB.isEmpty());

        final var firstSnapshot = renderIrBackendSnapshot(first);
        final var secondSnapshot = renderIrBackendSnapshot(second);
        assertEquals(firstSnapshot, secondSnapshot);
        assertGolden("golden/frontend-irbackend.txt", firstSnapshot);
    }

    private String renderIrBackendSnapshot(final p.studio.compiler.models.IRBackendFile file) {
        final var out = new StringBuilder();
        out.append("callables=").append(file.callableSignatures().size()).append('\n');
        for (var i = 0; i < file.callableSignatures().size(); i++) {
            final var callable = file.callableSignatures().get(i);
            out.append(i)
                    .append(':')
                    .append(callable.moduleId().isNone() ? "-" : callable.moduleId().getIndex())
                    .append("::")
                    .append(callable.callableName())
                    .append('/')
                    .append(callable.arity())
                    .append('|')
                    .append(callable.typeShape())
                    .append('\n');
        }
        out.append("intrinsics=").append(file.intrinsicPool().size()).append('\n');
        for (var i = 0; i < file.executableFunctions().size(); i++) {
            final var fn = file.executableFunctions().get(i);
            out.append("fn#")
                    .append(i)
                    .append(':')
                    .append(fn.moduleId().isNone() ? "-" : fn.moduleId().getIndex())
                    .append("::")
                    .append(fn.callableName())
                    .append(" id=")
                    .append(fn.callableId().getId())
                    .append('\n');
            for (final var instruction : fn.instructions()) {
                out.append("  ")
                        .append(instruction.kind())
                        .append(" calleeId=")
                        .append(instruction.calleeCallableId() == null ? "-" : instruction.calleeCallableId().getId())
                        .append(" intrinsicId=")
                        .append(instruction.intrinsicCall() == null ? "-" : instruction.intrinsicCall().intrinsicId().getId())
                        .append('\n');
            }
        }
        return out.toString();
    }

    private void assertGolden(
            final String relativeResourcePath,
            final String actual) throws IOException {
        final var goldenPath = Path.of("src/test/resources").resolve(relativeResourcePath);
        final var updateGolden = Boolean.getBoolean("golden.update")
                || "true".equalsIgnoreCase(System.getenv("GOLDEN_UPDATE"));
        if (!Files.exists(goldenPath)) {
            Files.createDirectories(goldenPath.getParent());
            Files.writeString(goldenPath, actual);
            return;
        }
        if (updateGolden) {
            Files.createDirectories(goldenPath.getParent());
            Files.writeString(goldenPath, actual);
            return;
        }
        final var expected = Files.readString(goldenPath);
        assertEquals(expected, actual);
    }
}
