package main

import (
	"bytes"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/marzeq/qk/attributes"
)

func TestArtifactCacheUsesRequestedLayoutAndBinaryFormat(t *testing.T) {
	t.Setenv("QK_CACHE_DIR", t.TempDir())
	cache, err := newArtifactCache(&Args{target: "aarch64-test", release: true})
	if err != nil {
		t.Fatal(err)
	}
	implementation := implementationHash("std.io", "; llvm")
	path := cache.modulePath(implementation)
	artifact := &cachedArtifact{ImplementationHash: implementation, Objects: map[string][]byte{"native": {1, 2, 3}}}
	if err := storeCachedArtifact(path, artifact); err != nil {
		t.Fatal(err)
	}
	if filepath.Base(path) != "blob" || filepath.Base(filepath.Dir(path)) != implementation {
		t.Fatalf("unexpected module artifact path %q", path)
	}
	raw, err := os.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.HasPrefix(raw, []byte(artifactMagic)) || bytes.HasPrefix(raw, []byte("{")) {
		t.Fatalf("artifact is not the custom binary format: %q", raw[:min(len(raw), 16)])
	}
	if bytes.Contains(raw, []byte("; llvm")) {
		t.Fatal("artifact contains LLVM IR")
	}
	loaded, ok := loadCachedArtifact(path, implementation)
	if !ok || !bytes.Equal(loaded.Objects["native"], []byte{1, 2, 3}) {
		t.Fatalf("artifact did not round trip: %#v", loaded)
	}
}

func TestSpecializationsAccumulateWithoutRewritingModuleBlob(t *testing.T) {
	t.Setenv("QK_CACHE_DIR", t.TempDir())
	cache, err := newArtifactCache(&Args{target: "x86_64-test"})
	if err != nil {
		t.Fatal(err)
	}
	implementation := implementationHash("std.io", "; base")
	modulePath := cache.modulePath(implementation)
	if err := storeCachedArtifact(modulePath, &cachedArtifact{ImplementationHash: implementation}); err != nil {
		t.Fatal(err)
	}
	before, err := os.ReadFile(modulePath)
	if err != nil {
		t.Fatal(err)
	}
	for _, request := range []string{"request-a", "request-b"} {
		path := cache.specializationPath(implementation, request)
		requestHash := implementationHash("specialization", request)
		if err := storeCachedArtifact(path, &cachedArtifact{ImplementationHash: requestHash}); err != nil {
			t.Fatal(err)
		}
	}
	after, err := os.ReadFile(modulePath)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(before, after) {
		t.Fatal("adding specializations rewrote the owning module blob")
	}
	entries, err := os.ReadDir(filepath.Join(filepath.Dir(modulePath), "specializations"))
	if err != nil || len(entries) != 2 {
		t.Fatalf("specializations did not accumulate: entries=%d err=%v", len(entries), err)
	}
}

func TestCorruptArtifactIsRejected(t *testing.T) {
	path := filepath.Join(t.TempDir(), "blob")
	if err := os.WriteFile(path, []byte(artifactMagic+"broken"), 0o644); err != nil {
		t.Fatal(err)
	}
	if _, ok := loadCachedArtifact(path, implementationHash("x", "y")); ok {
		t.Fatal("corrupt artifact was accepted")
	}
}

func TestConfigurationSeparatesIncompatibleBuilds(t *testing.T) {
	t.Setenv("QK_CACHE_DIR", t.TempDir())
	debugCache, err := newArtifactCache(&Args{target: "x86_64-test"})
	if err != nil {
		t.Fatal(err)
	}
	releaseCache, err := newArtifactCache(&Args{target: "x86_64-test", release: true})
	if err != nil {
		t.Fatal(err)
	}
	if debugCache.root == releaseCache.root {
		t.Fatal("release mode did not change the configuration namespace")
	}
}

func TestBuildSnapshotRoundTrip(t *testing.T) {
	t.Setenv("QK_CACHE_DIR", t.TempDir())
	cache, err := newArtifactCache(&Args{target: "x86_64-test"})
	if err != nil {
		t.Fatal(err)
	}
	implementation := implementationHash("main", "; module")
	modulePath := cache.modulePath(implementation)
	if err := storeCachedArtifact(modulePath, &cachedArtifact{
		ImplementationHash: implementation, Objects: map[string][]byte{cachedNativeObjectKey: {1}},
	}); err != nil {
		t.Fatal(err)
	}
	relative, err := filepath.Rel(cache.root, modulePath)
	if err != nil {
		t.Fatal(err)
	}
	want := &cachedBuildSnapshot{
		Modules:   []cachedBuildEntry{{Name: "main", Path: relative}},
		Links:     []attributes.Link{{Kind: attributes.LinkSystem, Value: "c"}},
		LinkRoots: []string{"entry"}, Warnings: []string{"warning"},
	}
	if err := cache.storeBuildSnapshot("build", want); err != nil {
		t.Fatal(err)
	}
	got, ok := cache.loadBuildSnapshot("build")
	if !ok || !reflect.DeepEqual(got, want) {
		t.Fatalf("snapshot did not round trip: got %#v, want %#v", got, want)
	}
}