package tests

import (
	"os"
	"testing"
	"time"

	"direct-dev.ru/gitea/GiteaAdmin/elowdb-go/pkg/linedb"
)

// mockMemcached — in-memory реализация MemcachedClient для тестов.
type mockMemcached struct {
	data map[string][]byte
}

func newMockMemcached() *mockMemcached {
	return &mockMemcached{data: make(map[string][]byte)}
}

func (m *mockMemcached) Get(key string) ([]byte, error) {
	v, ok := m.data[key]
	if !ok {
		return nil, nil
	}
	return v, nil
}

func (m *mockMemcached) Set(key string, value []byte, _ int) error {
	m.data[key] = value
	return nil
}

func (m *mockMemcached) Delete(key string) error {
	delete(m.data, key)
	return nil
}

func setupIndexedCollection(t *testing.T, indexStore linedb.IndexStore) (*linedb.LineDb, func()) {
	t.Helper()
	os.RemoveAll("./testdata")
	opts := &linedb.LineDbOptions{}
	if indexStore != nil {
		opts.IndexStore = indexStore
	}
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 100,
		CacheTTL:  time.Minute,
		DBFolder:  "./data/test-linedb-index",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  []string{"id", "email", "name"},
			},
		},
	}
	db := linedb.NewLineDb(opts)
	if err := db.Init(false, initOptions); err != nil {
		t.Fatalf("Init failed: %v", err)
	}
	return db, func() { db.Close(); os.RemoveAll("./testdata") }
}

func TestIndexInMemoryReadByFilter(t *testing.T) {
	db, cleanup := setupIndexedCollection(t, nil) // nil = auto in-memory
	defer cleanup()

	opts := linedb.LineDbAdapterOptions{}

	if err := db.Insert(map[string]any{"name": "alice", "email": "alice@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 1 failed: %v", err)
	}
	if err := db.Insert(map[string]any{"name": "bob", "email": "bob@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 2 failed: %v", err)
	}
	if err := db.Insert(map[string]any{"name": "charlie", "email": "charlie@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 3 failed: %v", err)
	}

	// Поиск по индексированному полю email
	found, err := db.ReadByFilter("email:bob@test.com,name:bob", "users", opts)
	// found, err := db.ReadByFilter(map[string]any{"email": "bob@test.com"}, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter failed: %v", err)
	}
	if len(found) != 1 {
		t.Fatalf("Expected 1 record for email bob@test.com, got %d", len(found))
	}
	if m, ok := found[0].(map[string]any); ok {
		if m["name"] != "bob" {
			t.Errorf("Expected name bob, got %v", m["name"])
		}
	}

	// Поиск по name
	found2, err := db.ReadByFilter(map[string]any{"name": "alice"}, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter name failed: %v", err)
	}
	if len(found2) != 1 {
		t.Fatalf("Expected 1 record for name alice, got %d", len(found2))
	}
}

// TestIndexEncodedCollectionCache проверяет шифрованную коллекцию и двойное чтение одного фильтра (кэш).
func TestIndexEncodedCollectionCache(t *testing.T) {
	err := os.RemoveAll("./data/test-linedb-index-enc")
	if err != nil {
		t.Logf("RemoveAll failed: %v", err)

	}
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 100,
		CacheTTL:  time.Minute * 10,
		DBFolder:  "./data/test-linedb-index-enc",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      512,
				IndexedFields:  []string{"id", "email", "name"},
				Encode:         false,
				EncodeKey:      "test-secret-key",
			},
		},
	}
	db := linedb.NewLineDb(nil)
	if err := db.Init(false, initOptions); err != nil {
		t.Fatalf("Init failed: %v", err)
	}
	defer func() { db.Close(); os.RemoveAll("./testdata") }()

	opts := linedb.LineDbAdapterOptions{}
	if err := db.Insert(map[string]any{"name": "alice", "email": "alice@secret.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 1 failed: %v", err)
	}
	if err := db.Insert(map[string]any{"name": "bob", "email": "bob@secret.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 2 failed: %v", err)
	}

	filter := map[string]any{"email": "bob@secret.com"}

	// Первое чтение — из файла (с дешифровкой)
	found1, err := db.ReadByFilter(filter, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter (1st) failed: %v", err)
	}
	if len(found1) != 1 {
		t.Fatalf("Expected 1 record, got %d", len(found1))
	}
	if m, ok := found1[0].(map[string]any); ok {
		if m["name"] != "bob" || m["email"] != "bob@secret.com" {
			t.Errorf("Expected name=bob email=bob@secret.com, got %+v", m)
		}
	}
	cacheSize1 := db.GetActualCacheSize()
	if cacheSize1 < 1 {
		t.Errorf("Expected cache to have at least 1 entry after first read, got %d", cacheSize1)
	}

	// Второе чтение — из кэша (должно вернуть те же данные без повторного чтения файла)
	found2, err := db.ReadByFilter(filter, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter (2nd, cached) failed: %v", err)
	}
	if len(found2) != 1 {
		t.Fatalf("Expected 1 record on second read, got %d", len(found2))
	}
	if m, ok := found2[0].(map[string]any); ok {
		if m["name"] != "bob" || m["email"] != "bob@secret.com" {
			t.Errorf("Expected name=bob email=bob@secret.com on cached read, got %+v", m)
		}
	}
	// Кэш не должен расти при повторном запросе с тем же ключом
	cacheSize2 := db.GetActualCacheSize()
	if cacheSize2 != cacheSize1 {
		t.Errorf("Cache size should stay same after cache hit: was %d, now %d", cacheSize1, cacheSize2)
	}

	// Обновляем запись (name) и проверяем, что кэш и индекс обновились
	_, err = db.Update(
		map[string]any{"name": "bob_updated"},
		"users",
		map[string]any{"email": "bob@secret.com"},
		opts,
	)
	if err != nil {
		t.Fatalf("Update failed: %v", err)
	}
	// После Update кэш сбрасывается — читаем снова, чтобы заполнить кэш актуальными данными
	found3, err := db.ReadByFilter(filter, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter after update failed: %v", err)
	}
	if len(found3) != 1 {
		t.Fatalf("Expected 1 record after update, got %d", len(found3))
	}
	if m, ok := found3[0].(map[string]any); ok && m["name"] != "bob_updated" {
		t.Errorf("Expected name=bob_updated after update, got %v", m["name"])
	}
	// Проверяем сырой кэш: в нём должна быть запись с name=bob_updated
	rawCache := db.GetCacheForTest("give_me_cache")
	if len(rawCache) == 0 {
		t.Error("Expected cache to have entries after read")
	}
	var foundInCache bool
	for _, v := range rawCache {
		arr, ok := v.([]any)
		if !ok || len(arr) != 1 {
			continue
		}
		m, ok := arr[0].(map[string]any)
		if !ok {
			continue
		}
		if m["email"] == "bob@secret.com" {
			foundInCache = true
			if m["name"] != "bob_updated" {
				t.Errorf("Expected cached name=bob_updated, got %v", m["name"])
			}
			break
		}
	}
	if !foundInCache {
		t.Error("Expected to find bob record in raw cache with updated name")
	}
	// Проверяем индекс: по email bob@secret.com должна быть одна позиция, по name bob_updated — тоже
	idxSnapshot := db.GetIndexSnapshotForTest("give_me_cache")
	if len(idxSnapshot) == 0 {
		t.Error("Expected index snapshot to have entries")
	}
	if emailIdx, ok := idxSnapshot["users:email"].(map[string][]linedb.IndexPosition); ok {
		if positions, ok := emailIdx["bob@secret.com"]; !ok || len(positions) != 1 {
			t.Errorf("Expected index users:email bob@secret.com to have 1 position, got %v", emailIdx["bob@secret.com"])
		}
	}
	if nameIdx, ok := idxSnapshot["users:name"].(map[string][]linedb.IndexPosition); ok {
		if positions, ok := nameIdx["bob_updated"]; !ok || len(positions) != 1 {
			t.Errorf("Expected index users:name bob_updated to have 1 position, got %v", nameIdx["bob_updated"])
		}
	}

	// Удаляем запись и проверяем, что в кэше и в индексе её больше нет
	_, err = db.Delete(map[string]any{"email": "bob@secret.com"}, "users", opts)
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}
	// После Delete кэш сбрасывается — читаем снова
	found4, err := db.ReadByFilter(filter, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter after delete failed: %v", err)
	}
	if len(found4) != 0 {
		t.Fatalf("Expected 0 records after delete, got %d", len(found4))
	}
	// В сыром кэше не должно остаться записи bob
	rawCache2 := db.GetCacheForTest("give_me_cache")
	for _, v := range rawCache2 {
		arr, ok := v.([]any)
		if !ok || len(arr) != 1 {
			continue
		}
		if m, ok := arr[0].(map[string]any); ok && m["email"] == "bob@secret.com" {
			t.Error("Cached result after delete should not contain bob record")
		}
	}
	// Индекс: bob@secret.com и bob_updated не должны быть в индексе (или пустые срезы)
	idxSnapshot2 := db.GetIndexSnapshotForTest("give_me_cache")
	if emailIdx, ok := idxSnapshot2["users:email"].(map[string][]linedb.IndexPosition); ok {
		if positions, has := emailIdx["bob@secret.com"]; has && len(positions) > 0 {
			t.Errorf("After delete, index users:email bob@secret.com should be empty, got %v", positions)
		}
	}
	if nameIdx, ok := idxSnapshot2["users:name"].(map[string][]linedb.IndexPosition); ok {
		if positions, has := nameIdx["bob_updated"]; has && len(positions) > 0 {
			t.Errorf("After delete, index users:name bob_updated should be empty, got %v", positions)
		}
	}
}

func TestIndexExplicitInMemory(t *testing.T) {
	store := linedb.NewInMemoryIndexStore()
	db, cleanup := setupIndexedCollection(t, store)
	defer cleanup()

	opts := linedb.LineDbAdapterOptions{}
	if err := db.Insert(map[string]any{"name": "x", "email": "x@y.com"}, "users", opts); err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	found, err := db.ReadByFilter(map[string]any{"email": "x@y.com"}, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter failed: %v", err)
	}
	if len(found) != 1 {
		t.Fatalf("Expected 1 record, got %d", len(found))
	}
}

func TestIndexMemcachedStore(t *testing.T) {
	mock := newMockMemcached()
	store, err := linedb.NewMemcachedIndexStore(linedb.MemcachedIndexStoreOptions{
		Client: mock,
	})
	if err != nil {
		t.Fatalf("NewMemcachedIndexStore: %v", err)
	}

	db, cleanup := setupIndexedCollection(t, store)
	defer cleanup()

	opts := linedb.LineDbAdapterOptions{}
	if err := db.Insert(map[string]any{"name": "mem", "email": "mem@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	found, err := db.ReadByFilter(map[string]any{"email": "mem@test.com"}, "users", opts)
	if err != nil {
		t.Fatalf("ReadByFilter failed: %v", err)
	}
	if len(found) != 1 {
		t.Fatalf("Expected 1 record, got %d", len(found))
	}

	// Проверяем, что в mockMemcached что-то записалось
	if len(mock.data) == 0 {
		t.Error("Expected memcached store to have data")
	}
}

func TestIndexUpdateRebuild(t *testing.T) {
	db, cleanup := setupIndexedCollection(t, nil)
	defer cleanup()

	opts := linedb.LineDbAdapterOptions{}
	if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert failed: %v", err)
	}

	_, err := db.Update(
		map[string]any{"email": "a_updated@test.com"},
		"users",
		map[string]any{"email": "a@test.com"},
		opts,
	)
	if err != nil {
		t.Fatalf("Update failed: %v", err)
	}

	// Старый email не должен находиться
	old, _ := db.ReadByFilter(map[string]any{"email": "a@test.com"}, "users", opts)
	if len(old) != 0 {
		t.Fatalf("Expected 0 records for old email, got %d", len(old))
	}
	// Новый — должен
	newFound, _ := db.ReadByFilter(map[string]any{"email": "a_updated@test.com"}, "users", opts)
	if len(newFound) != 1 {
		t.Fatalf("Expected 1 record for new email, got %d", len(newFound))
	}
}

func TestIndexDeleteRebuild(t *testing.T) {
	db, cleanup := setupIndexedCollection(t, nil)
	defer cleanup()

	opts := linedb.LineDbAdapterOptions{}
	if err := db.Insert(map[string]any{"name": "del", "email": "del@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert failed: %v", err)
	}
	if err := db.Insert(map[string]any{"name": "keep", "email": "keep@test.com"}, "users", opts); err != nil {
		t.Fatalf("Insert 2 failed: %v", err)
	}

	_, err := db.Delete(map[string]any{"email": "del@test.com"}, "users", opts)
	if err != nil {
		t.Fatalf("Delete failed: %v", err)
	}

	found, _ := db.ReadByFilter(map[string]any{"email": "del@test.com"}, "users", opts)
	if len(found) != 0 {
		t.Fatalf("Expected 0 after delete, got %d", len(found))
	}
	kept, _ := db.ReadByFilter(map[string]any{"email": "keep@test.com"}, "users", opts)
	if len(kept) != 1 {
		t.Fatalf("Expected 1 kept record, got %d", len(kept))
	}
}

func TestIndexNoIndexedFields(t *testing.T) {
	os.RemoveAll("./testdata")
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 100,
		CacheTTL:  time.Minute,
		DBFolder:  "./testdata",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  nil, // без индексов
			},
		},
	}
	db := linedb.NewLineDb(nil)
	if err := db.Init(false, initOptions); err != nil {
		t.Fatalf("Init failed: %v", err)
	}
	defer func() { db.Close(); os.RemoveAll("./testdata") }()

	if err := db.Insert(map[string]any{"name": "a", "email": "a@test.com"}, "users", linedb.LineDbAdapterOptions{}); err != nil {
		t.Fatalf("Insert failed: %v", err)
	}
	// ReadByFilter всё равно работает (полный скан)
	found, err := db.ReadByFilter(map[string]any{"email": "a@test.com"}, "users", linedb.LineDbAdapterOptions{})
	if err != nil {
		t.Fatalf("ReadByFilter failed: %v", err)
	}
	if len(found) != 1 {
		t.Fatalf("Expected 1 record, got %d", len(found))
	}
}
