package sqlitevec

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

	sqlite_vec "github.com/asg017/sqlite-vec-go-bindings/cgo"
	"github.com/lukaszraczylo/claude-mnemonic/internal/embedding"
	_ "github.com/mattn/go-sqlite3"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// testDB creates a test SQLite database with the vectors table.
func testDB(t *testing.T) (*sql.DB, func()) {
	t.Helper()

	// Create temp directory
	tmpDir, err := os.MkdirTemp("", "sqlitevec-test-*")
	require.NoError(t, err)

	dbPath := filepath.Join(tmpDir, "test.db")
	db, err := sql.Open("sqlite3", dbPath)
	require.NoError(t, err)

	// Enable sqlite-vec
	sqlite_vec.Auto()

	// Create vectors table (matches production schema)
	_, err = db.Exec(`
		CREATE VIRTUAL TABLE IF NOT EXISTS vectors USING vec0(
			doc_id TEXT PRIMARY KEY,
			embedding float[384],
			sqlite_id INTEGER,
			doc_type TEXT,
			field_type TEXT,
			project TEXT,
			scope TEXT,
			model_version TEXT
		)
	`)
	require.NoError(t, err)

	cleanup := func() {
		db.Close()
		os.RemoveAll(tmpDir)
	}

	return db, cleanup
}

// testEmbeddingService creates a test embedding service.
func testEmbeddingService(t *testing.T) (*embedding.Service, func()) {
	t.Helper()

	svc, err := embedding.NewService()
	require.NoError(t, err)

	cleanup := func() {
		svc.Close()
	}

	return svc, cleanup
}

func TestNewClient_Success(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)
	assert.NotNil(t, client)
}

func TestNewClient_NilDB(t *testing.T) {
	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: nil}, embedSvc)
	assert.Error(t, err)
	assert.Nil(t, client)
	assert.Contains(t, err.Error(), "database connection required")
}

func TestNewClient_NilEmbedding(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	client, err := NewClient(Config{DB: db}, nil)
	assert.Error(t, err)
	assert.Nil(t, client)
	assert.Contains(t, err.Error(), "embedding service required")
}

func TestClient_AddDocuments_Empty(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	err = client.AddDocuments(context.Background(), []Document{})
	require.NoError(t, err)
}

func TestClient_AddDocuments_Single(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	docs := []Document{
		{
			ID:      "obs-1-title",
			Content: "This is a test observation about authentication.",
			Metadata: map[string]any{
				"sqlite_id":  int64(1),
				"doc_type":   "observation",
				"field_type": "title",
				"project":    "test-project",
				"scope":      "project",
			},
		},
	}

	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Verify document was inserted
	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM vectors WHERE doc_id = ?", "obs-1-title").Scan(&count)
	require.NoError(t, err)
	assert.Equal(t, 1, count)
}

func TestClient_AddDocuments_Multiple(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	docs := []Document{
		{
			ID:      "obs-1-title",
			Content: "Authentication flow implementation.",
			Metadata: map[string]any{
				"sqlite_id":  int64(1),
				"doc_type":   "observation",
				"field_type": "title",
				"project":    "test-project",
				"scope":      "project",
			},
		},
		{
			ID:      "obs-1-narrative",
			Content: "We implemented JWT-based authentication.",
			Metadata: map[string]any{
				"sqlite_id":  int64(1),
				"doc_type":   "observation",
				"field_type": "narrative",
				"project":    "test-project",
				"scope":      "project",
			},
		},
		{
			ID:      "obs-2-title",
			Content: "Database optimization.",
			Metadata: map[string]any{
				"sqlite_id":  int64(2),
				"doc_type":   "observation",
				"field_type": "title",
				"project":    "test-project",
				"scope":      "global",
			},
		},
	}

	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Verify all documents were inserted
	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM vectors").Scan(&count)
	require.NoError(t, err)
	assert.Equal(t, 3, count)
}

func TestClient_DeleteDocuments_Empty(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	err = client.DeleteDocuments(context.Background(), []string{})
	require.NoError(t, err)
}

func TestClient_DeleteDocuments_Existing(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Add documents first
	docs := []Document{
		{
			ID:      "doc-1",
			Content: "First document.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
			},
		},
		{
			ID:      "doc-2",
			Content: "Second document.",
			Metadata: map[string]any{
				"sqlite_id": int64(2),
				"doc_type":  "observation",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Delete one document
	err = client.DeleteDocuments(context.Background(), []string{"doc-1"})
	require.NoError(t, err)

	// Verify only one remains
	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM vectors").Scan(&count)
	require.NoError(t, err)
	assert.Equal(t, 1, count)
}

func TestClient_Query_Basic(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Add some test documents
	docs := []Document{
		{
			ID:      "obs-1",
			Content: "Authentication and login security implementation.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
				"project":   "test-project",
				"scope":     "project",
			},
		},
		{
			ID:      "obs-2",
			Content: "Database query optimization techniques.",
			Metadata: map[string]any{
				"sqlite_id": int64(2),
				"doc_type":  "observation",
				"project":   "test-project",
				"scope":     "project",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Query for authentication-related content
	results, err := client.Query(context.Background(), "login authentication", 10, nil)
	require.NoError(t, err)

	assert.NotEmpty(t, results)
	assert.LessOrEqual(t, len(results), 10)

	// First result should be the authentication document (higher similarity)
	assert.Equal(t, "obs-1", results[0].ID)
}

func TestClient_Query_WithDocTypeFilter(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Add documents of different types
	docs := []Document{
		{
			ID:      "obs-1",
			Content: "Test content for observation.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
				"project":   "test-project",
			},
		},
		{
			ID:      "summary-1",
			Content: "Test content for summary.",
			Metadata: map[string]any{
				"sqlite_id": int64(10),
				"doc_type":  "session_summary",
				"project":   "test-project",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Query with doc_type filter
	where := map[string]any{"doc_type": "observation"}
	results, err := client.Query(context.Background(), "test content", 10, where)
	require.NoError(t, err)

	// Should only return observation documents
	for _, r := range results {
		docType, _ := r.Metadata["doc_type"].(string)
		assert.Equal(t, "observation", docType)
	}
}

func TestClient_Query_WithProjectFilter(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Add documents from different projects
	docs := []Document{
		{
			ID:      "obs-1",
			Content: "Project A authentication content.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
				"project":   "project-a",
				"scope":     "project",
			},
		},
		{
			ID:      "obs-2",
			Content: "Project B database content.",
			Metadata: map[string]any{
				"sqlite_id": int64(2),
				"doc_type":  "observation",
				"project":   "project-b",
				"scope":     "project",
			},
		},
		{
			ID:      "obs-3",
			Content: "Global security best practices.",
			Metadata: map[string]any{
				"sqlite_id": int64(3),
				"doc_type":  "observation",
				"project":   "project-b",
				"scope":     "global",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs)
	require.NoError(t, err)

	// Query without project filter to verify all docs are there
	results, err := client.Query(context.Background(), "authentication security", 10, nil)
	require.NoError(t, err)
	assert.NotEmpty(t, results, "Should find some results")
}

func TestClient_IsConnected(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	assert.True(t, client.IsConnected())
}

func TestClient_Close(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	err = client.Close()
	require.NoError(t, err)
}

func TestConfig_Fields(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	cfg := Config{DB: db}
	assert.Equal(t, db, cfg.DB)
}

func TestClient_UpdateDocument_DeleteThenAdd(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Add document
	docs1 := []Document{
		{
			ID:      "doc-1",
			Content: "Original content.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs1)
	require.NoError(t, err)

	// Delete then add with new content (proper update pattern)
	err = client.DeleteDocuments(context.Background(), []string{"doc-1"})
	require.NoError(t, err)

	docs2 := []Document{
		{
			ID:      "doc-1",
			Content: "Updated content.",
			Metadata: map[string]any{
				"sqlite_id": int64(1),
				"doc_type":  "observation",
			},
		},
	}
	err = client.AddDocuments(context.Background(), docs2)
	require.NoError(t, err)

	// Should have exactly 1 document
	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM vectors WHERE doc_id = ?", "doc-1").Scan(&count)
	require.NoError(t, err)
	assert.Equal(t, 1, count)
}

func TestClient_DeleteDocuments_NonExistent(t *testing.T) {
	db, dbCleanup := testDB(t)
	defer dbCleanup()

	embedSvc, embedCleanup := testEmbeddingService(t)
	defer embedCleanup()

	client, err := NewClient(Config{DB: db}, embedSvc)
	require.NoError(t, err)

	// Deleting non-existent document should not error
	err = client.DeleteDocuments(context.Background(), []string{"non-existent-id"})
	require.NoError(t, err)
}
