package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

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

// Пример работы с партициями + индексируемой коллекцией.
//
// Запуск:
//
//	go run ./examples/partitions/main.go
//
// Важно:
//   - В текущей реализации индексы строятся для "обычных" коллекций.
//   - Партиционированные коллекции (партиции) создаются динамически и сейчас не индексируются
//     (см. getPartitionAdapter: JSONLFileOptions{CollectionName: partitionName} без IndexedFields).
func main() {
	dbDir := filepath.Join(".", "examples", "partitions", "data")
	_ = os.RemoveAll(dbDir)
	if err := os.MkdirAll(dbDir, 0755); err != nil {
		log.Fatalf("mkdir: %v", err)
	}

	// Настройка:
	// - users: обычная коллекция с индексами
	// - events: базовая коллекция для партиций (сама по себе не используется для записи),
	//   а реальные данные будут в events_<tenant>.jsonl
	initOptions := &linedb.LineDbInitOptions{
		DBFolder: dbDir,
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      512,
				IndexedFields:  []string{"id", "email"},
			},
			{
				CollectionName: "events",
				AllocSize:      512,
				IndexedFields:  []string{"id", "type"},
			},
		},
		Partitions: []linedb.PartitionCollection{
			{
				CollectionName: "events",
				PartIDFn: func(v any) string {
					m, ok := v.(map[string]any)
					if !ok {
						return "unknown"
					}
					tenant, ok := m["tenant"].(string)
					if !ok || tenant == "" {
						return "unknown"
					}
					return tenant
				},
			},
		},
	}

	db := linedb.NewLineDb(&linedb.LineDbOptions{
		IndexStore: linedb.NewInMemoryIndexStore(),
	})
	if err := db.Init(true, initOptions); err != nil {
		log.Fatalf("Init failed: %v", err)
	}
	defer db.Close()

	fmt.Println("=== 1) Индексируемая коллекция users ===")
	users := []any{
		map[string]any{"id": 1, "email": "a@example.com", "name": "Alice", "createdAt": time.Now().Format(time.RFC3339Nano)},
		map[string]any{"id": 2, "email": "b@example.com", "name": "Bob", "createdAt": time.Now().Format(time.RFC3339Nano)},
	}
	// Используем Write + DoIndexing: true, чтобы индекс был актуален сразу после записи.
	if err := db.Write(users, "users", linedb.LineDbAdapterOptions{DoIndexing: true}); err != nil {
		log.Fatalf("Write users failed: %v", err)
	}

	byEmail, err := db.ReadByFilter(map[string]any{"email": "a@example.com"}, "users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter users by email failed: %v", err)
	}
	mustLen("users by email", byEmail, 1)

	byID, err := db.ReadByFilter(map[string]any{"id": 2}, "users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter users by id failed: %v", err)
	}
	mustLen("users by id", byID, 1)

	fmt.Println("OK: users inserted and searchable by indexed fields (id/email).")

	fmt.Println("\n=== 2) Партиционированная коллекция events (events_<tenant>) ===")
	events := []any{
		map[string]any{"id": 1, "tenant": "A", "type": "signup", "status": "new", "ts": time.Now().Unix()},
		map[string]any{"id": 2, "tenant": "A", "type": "purchase", "status": "new", "ts": time.Now().Unix()},
		map[string]any{"id": 3, "tenant": "B", "type": "signup", "status": "new", "ts": time.Now().Unix()},
	}
	if err := db.Insert(events, "events", linedb.LineDbAdapterOptions{}); err != nil {
		log.Fatalf("Insert events failed: %v", err)
	}

	tenantA, err := db.ReadByFilter(map[string]any{"type": "signup", "tenant": "A"}, "events", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter events tenant A failed: %v", err)
	}
	mustLen("events tenant A after insert", tenantA, 2)

	tenantB, err := db.ReadByFilter(map[string]any{"type": "signup", "tenant": "B"}, "events", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter events tenant B failed: %v", err)
	}
	mustLen("events tenant B after insert", tenantB, 1)

	fmt.Println("OK: события разложены по партициям (A и B).")

	fmt.Println("\n=== 3) Update по всем партициям ===")
	updated, err := db.Update(
		map[string]any{"status": "processed"},
		"events",
		map[string]any{"tenant": "A"},
		linedb.LineDbAdapterOptions{},
	)
	if err != nil {
		log.Fatalf("Update events failed: %v", err)
	}
	mustLen("updated events for tenant A", updated, 2)

	processedA, err := db.ReadByFilter(map[string]any{"tenant": "A", "status": "processed"}, "events", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter processed events failed: %v", err)
	}
	mustLen("processed events for tenant A", processedA, 2)

	fmt.Println("OK: обновление затронуло записи в партиции A.")

	fmt.Println("\n=== 4) Delete по всем партициям ===")
	deleted, err := db.Delete(map[string]any{"id": 3}, "events", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("Delete events failed: %v", err)
	}
	mustLen("deleted events id=3", deleted, 1)

	allRemaining, err := db.ReadByFilter(nil, "events", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Fatalf("ReadByFilter all remaining events failed: %v", err)
	}
	mustLen("remaining events after delete", allRemaining, 2)

	fmt.Printf("\nГотово. Данные примера в: %s\n", dbDir)
}

func mustLen(label string, got []any, want int) {
	if len(got) != want {
		log.Fatalf("%s: expected %d, got %d (%v)", label, want, len(got), got)
	}
}
