package shellknowledge

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"log/slog"

	"github.com/enmanuel/agents/pkg/knowledge"
)

func testStore(t *testing.T) (*FileStore, string) {
	t.Helper()
	dir := t.TempDir()
	knowledgeDir := filepath.Join(dir, "knowledge")
	dbPath := filepath.Join(dir, "data", "knowledge.db")
	logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
	store, err := New(knowledgeDir, dbPath, logger)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { store.Close() })
	return store, knowledgeDir
}

func TestValidSlug(t *testing.T) {
	tests := []struct {
		slug string
		want bool
	}{
		{"go-patterns", true},
		{"ab", true},
		{"a-b", true},
		{"abc123", true},
		{"a", false},             // too short
		{"A-B", false},           // uppercase
		{"-bad", false},          // starts with hyphen
		{"bad-", false},          // ends with hyphen
		{"has space", false},     // space
		{"has_underscore", false}, // underscore
		{"", false},
	}
	for _, tt := range tests {
		if got := ValidSlug(tt.slug); got != tt.want {
			t.Errorf("ValidSlug(%q) = %v, want %v", tt.slug, got, tt.want)
		}
	}
}

func TestPutAndGet(t *testing.T) {
	store, _ := testStore(t)
	ctx := context.Background()

	doc := knowledge.Document{
		Slug:    "test-doc",
		Content: "# Test Document\n\nThis is a test.",
	}

	if err := store.Put(ctx, doc); err != nil {
		t.Fatal(err)
	}

	got, err := store.Get(ctx, "test-doc")
	if err != nil {
		t.Fatal(err)
	}
	if got.Content != doc.Content {
		t.Errorf("content mismatch: got %q, want %q", got.Content, doc.Content)
	}
	if got.Title != "Test Document" {
		t.Errorf("title = %q, want %q", got.Title, "Test Document")
	}
}

func TestPutInvalidSlug(t *testing.T) {
	store, _ := testStore(t)
	ctx := context.Background()

	err := store.Put(ctx, knowledge.Document{Slug: "BAD", Content: "test"})
	if err == nil {
		t.Error("expected error for invalid slug")
	}
}

func TestPutTooLarge(t *testing.T) {
	store, _ := testStore(t)
	ctx := context.Background()

	bigContent := make([]byte, 65*1024)
	for i := range bigContent {
		bigContent[i] = 'x'
	}
	err := store.Put(ctx, knowledge.Document{Slug: "too-big", Content: string(bigContent)})
	if err == nil {
		t.Error("expected error for oversized document")
	}
}

func TestSyncAndSearch(t *testing.T) {
	store, knowledgeDir := testStore(t)
	ctx := context.Background()

	// Write files directly to disk
	os.WriteFile(filepath.Join(knowledgeDir, "go-patterns.md"),
		[]byte("# Go Patterns\n\nUse interfaces for dependency injection."), 0o644)
	os.WriteFile(filepath.Join(knowledgeDir, "matrix-tips.md"),
		[]byte("# Matrix Tips\n\nUse mautrix-go for Matrix bots."), 0o644)

	if err := store.Sync(ctx); err != nil {
		t.Fatal(err)
	}

	// Search for "interfaces"
	results, err := store.Search(ctx, "interfaces", 5)
	if err != nil {
		t.Fatal(err)
	}
	if len(results) == 0 {
		t.Fatal("expected at least 1 search result")
	}
	if results[0].Slug != "go-patterns" {
		t.Errorf("expected slug go-patterns, got %q", results[0].Slug)
	}
}

func TestList(t *testing.T) {
	store, _ := testStore(t)
	ctx := context.Background()

	// Empty initially
	docs, err := store.List(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if len(docs) != 0 {
		t.Errorf("expected 0 docs, got %d", len(docs))
	}

	// Add two docs
	store.Put(ctx, knowledge.Document{Slug: "alpha", Content: "# Alpha\nContent A"})
	store.Put(ctx, knowledge.Document{Slug: "beta", Content: "# Beta\nContent B"})

	docs, err = store.List(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if len(docs) != 2 {
		t.Fatalf("expected 2 docs, got %d", len(docs))
	}
}

func TestDelete(t *testing.T) {
	store, knowledgeDir := testStore(t)
	ctx := context.Background()

	store.Put(ctx, knowledge.Document{Slug: "to-delete", Content: "# Delete Me\nGoodbye"})

	// Verify file exists
	if _, err := os.Stat(filepath.Join(knowledgeDir, "to-delete.md")); err != nil {
		t.Fatal("file should exist after Put")
	}

	if err := store.Delete(ctx, "to-delete"); err != nil {
		t.Fatal(err)
	}

	// File removed
	if _, err := os.Stat(filepath.Join(knowledgeDir, "to-delete.md")); !os.IsNotExist(err) {
		t.Error("file should be removed after Delete")
	}

	// Not in index
	_, err := store.Get(ctx, "to-delete")
	if err == nil {
		t.Error("expected error for deleted document")
	}
}

func TestGetNotFound(t *testing.T) {
	store, _ := testStore(t)
	ctx := context.Background()

	_, err := store.Get(ctx, "nonexistent")
	if err == nil {
		t.Error("expected error for nonexistent document")
	}
}

func TestExtractTitle(t *testing.T) {
	tests := []struct {
		content string
		slug    string
		want    string
	}{
		{"# My Title\nBody", "slug", "My Title"},
		{"No heading here", "my-doc", "My doc"},
		{"", "empty-doc", "Empty doc"},
		{"\n\n# Late Title\n", "slug", "Late Title"},
	}
	for _, tt := range tests {
		got := extractTitle(tt.content, tt.slug)
		if got != tt.want {
			t.Errorf("extractTitle(%q, %q) = %q, want %q", tt.content, tt.slug, got, tt.want)
		}
	}
}
