package store_test

import (
	"context"
	"errors"
	"os"
	"strings"
	"sync"
	"testing"

	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/contracts/graph"
	"bit.admedia.com/scm/ad/adpilot-indexing-code-parser.com/internal/store"
)

func sampleArtifact(filePath string) graph.Artifact {
	return graph.Artifact{
		SchemaVersion: graph.SchemaVersionV1,
		RepoID:        "ad/adpilot-indexing-code-parser.com",
		SnapshotID:    "snap_01HZX",
		CommitSHA:     "a3f9c12b",
		Nodes: []graph.Node{
			{
				ID:       "file:" + filePath,
				Kind:     graph.NodeKindFile,
				Name:     filePath,
				Language: "go",
				Path:     filePath,
			},
			{
				ID:       "symbol:" + filePath + "#main",
				Kind:     graph.NodeKindSymbol,
				Name:     "main",
				Language: "go",
				Path:     filePath,
			},
		},
		Edges: []graph.Edge{
			{
				ID:       "edge:" + filePath + "#defines#main",
				Kind:     graph.EdgeKindDefines,
				SourceID: "file:" + filePath,
				TargetID: "symbol:" + filePath + "#main",
			},
		},
	}
}

func TestFilesystemStoreSaveLoadRoundTrip(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	original := sampleArtifact("internal/main.go")
	id, uri, err := fs.SaveArtifact(context.Background(), original)
	if err != nil {
		t.Fatalf("SaveArtifact: %v", err)
	}

	loaded, err := fs.GetArtifact(context.Background(), id)
	if err != nil {
		t.Fatalf("GetArtifact: %v", err)
	}

	if loaded.RepoID != original.RepoID {
		t.Fatalf("repo_id: got %q, want %q", loaded.RepoID, original.RepoID)
	}
	if len(loaded.Nodes) != len(original.Nodes) {
		t.Fatalf("nodes: got %d, want %d", len(loaded.Nodes), len(original.Nodes))
	}
	if !strings.HasPrefix(uri, "file://") {
		t.Fatalf("uri: got %q, want file:// prefix", uri)
	}
}

func TestFilesystemStoreDeterministicID(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	artifact := sampleArtifact("pkg/parser.go")
	id1, _, err := fs.SaveArtifact(context.Background(), artifact)
	if err != nil {
		t.Fatalf("first SaveArtifact: %v", err)
	}

	id2, _, err := fs.SaveArtifact(context.Background(), artifact)
	if err != nil {
		t.Fatalf("second SaveArtifact: %v", err)
	}

	if id1 != id2 {
		t.Fatalf("expected deterministic id, got %q and %q", id1, id2)
	}
}

func TestFilesystemStoreDifferentFilesDifferentIDs(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	id1, _, err := fs.SaveArtifact(context.Background(), sampleArtifact("a.go"))
	if err != nil {
		t.Fatalf("SaveArtifact a.go: %v", err)
	}

	id2, _, err := fs.SaveArtifact(context.Background(), sampleArtifact("b.go"))
	if err != nil {
		t.Fatalf("SaveArtifact b.go: %v", err)
	}

	if id1 == id2 {
		t.Fatalf("expected different ids for different files, both %q", id1)
	}
}

func TestFilesystemStoreMissingFileNode(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	artifact := graph.Artifact{
		SchemaVersion: graph.SchemaVersionV1,
		RepoID:        "ad/repo",
		SnapshotID:    "snap_1",
		CommitSHA:     "abc123",
		Nodes: []graph.Node{
			{ID: "symbol:main", Kind: graph.NodeKindSymbol, Name: "main"},
		},
	}

	if _, _, err := fs.SaveArtifact(context.Background(), artifact); err == nil {
		t.Fatal("expected error for missing file node")
	}
}

func TestFilesystemStoreGetNotFound(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	_, err = fs.GetArtifact(context.Background(), "art_snap_01HZX_deadbeef")
	if err == nil {
		t.Fatal("expected error for missing artifact")
	}
	if !errors.Is(err, store.ErrArtifactNotFound) {
		t.Fatalf("expected ErrArtifactNotFound, got %v", err)
	}
}

func TestGetArtifactMetadata(t *testing.T) {
	dir := t.TempDir()
	fs, err := store.NewFilesystemStore(dir)
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	id, _, err := fs.SaveArtifact(context.Background(), sampleArtifact("cmd/main.go"))
	if err != nil {
		t.Fatalf("SaveArtifact: %v", err)
	}

	metadata, err := fs.GetArtifactMetadata(context.Background(), id)
	if err != nil {
		t.Fatalf("GetArtifactMetadata: %v", err)
	}
	if metadata.ArtifactID != id {
		t.Fatalf("artifact_id: got %q, want %q", metadata.ArtifactID, id)
	}
	if metadata.FilePath != "cmd/main.go" {
		t.Fatalf("file_path: got %q, want cmd/main.go", metadata.FilePath)
	}
	if metadata.NodeCount != 2 {
		t.Fatalf("node_count: got %d, want 2", metadata.NodeCount)
	}
	if metadata.ArtifactURI == "" {
		t.Fatal("expected artifact_uri")
	}
}

func TestFilesystemStoreInvalidArtifactID(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	for _, id := range []string{"", "../escape", "bad/id", `bad\id`} {
		if _, err := fs.GetArtifact(context.Background(), id); err == nil {
			t.Fatalf("expected error for invalid artifact id %q", id)
		} else if !errors.Is(err, store.ErrInvalidArtifactID) {
			t.Fatalf("expected ErrInvalidArtifactID for %q, got %v", id, err)
		}
	}
}

func TestFilesystemStoreConcurrentSave(t *testing.T) {
	fs, err := store.NewFilesystemStore(t.TempDir())
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	const workers = 16
	var wg sync.WaitGroup
	errCh := make(chan error, workers)

	for i := 0; i < workers; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			filePath := "internal/file_" + string(rune('a'+i)) + ".go"
			_, _, err := fs.SaveArtifact(context.Background(), sampleArtifact(filePath))
			if err != nil {
				errCh <- err
			}
		}(i)
	}

	wg.Wait()
	close(errCh)

	for err := range errCh {
		t.Fatalf("concurrent SaveArtifact: %v", err)
	}
}

func TestFilesystemStoreURIFormat(t *testing.T) {
	dir := t.TempDir()
	fs, err := store.NewFilesystemStore(dir)
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	id, uri, err := fs.SaveArtifact(context.Background(), sampleArtifact("cmd/main.go"))
	if err != nil {
		t.Fatalf("SaveArtifact: %v", err)
	}

	if !strings.HasPrefix(uri, "file://") {
		t.Fatalf("uri prefix: got %q", uri)
	}
	if !strings.HasSuffix(uri, id+".json") {
		t.Fatalf("uri suffix: got %q, want %q.json", uri, id)
	}
}

func TestFilesystemStoreReady(t *testing.T) {
	dir := t.TempDir()
	fs, err := store.NewFilesystemStore(dir)
	if err != nil {
		t.Fatalf("NewFilesystemStore: %v", err)
	}

	if !fs.Ready(context.Background()) {
		t.Fatal("expected ready store")
	}

	readOnlyDir := t.TempDir()
	if err := os.Chmod(readOnlyDir, 0o555); err != nil {
		t.Fatalf("chmod: %v", err)
	}
	t.Cleanup(func() { _ = os.Chmod(readOnlyDir, 0o755) })

	readOnlyStore, err := store.NewFilesystemStore(readOnlyDir)
	if err != nil {
		t.Fatalf("NewFilesystemStore read-only: %v", err)
	}
	if readOnlyStore.Ready(context.Background()) {
		t.Fatal("expected not ready for read-only store path")
	}
}
