package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

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

// User представляет пользователя
type User struct {
	ID        int    `json:"id"`
	Username  string `json:"username"`
	Email     string `json:"email"`
	IsActive  bool   `json:"isActive"`
	Role      string `json:"role"`
	CreatedAt int64  `json:"createdAt"`
}

// Product представляет продукт
type Product struct {
	ID        int     `json:"id"`
	Name      string  `json:"name"`
	Price     float64 `json:"price"`
	Category  string  `json:"category"`
	InStock   bool    `json:"inStock"`
	SellerID  int     `json:"sellerId"`
	CreatedAt int64   `json:"createdAt"`
}

func main() {
	// Очищаем тестовые данные
	os.RemoveAll("./data/test-linedb-custom-json")

	fmt.Println("=== LineDb Custom JSON Serialization Demo ===")

	// Получаем номера тестов из аргументов командной строки
	testNumbers := parseTestNumbers()

	// Выполняем тесты
	if len(testNumbers) == 0 {
		// Если номера не указаны, выполняем все тесты
		runAllTests()
	} else {
		// Выполняем только указанные тесты
		runSelectedTests(testNumbers)
	}

	fmt.Println("\n=== Все тесты завершены ===")
}

// parseTestNumbers парсит номера тестов из аргументов командной строки
func parseTestNumbers() []int {
	if len(os.Args) < 2 {
		return []int{}
	}

	// Получаем аргумент после имени программы
	arg := os.Args[1]

	// Проверяем, что это не флаг помощи
	if arg == "-h" || arg == "--help" || arg == "help" {
		printUsage()
		return []int{}
	}

	// Разбиваем по запятой
	parts := strings.Split(arg, ",")
	var numbers []int

	for _, part := range parts {
		part = strings.TrimSpace(part)
		if part == "" {
			continue
		}

		num, err := strconv.Atoi(part)
		if err != nil {
			fmt.Printf("Ошибка: '%s' не является числом\n", part)
			printUsage()
			return []int{}
		}

		// Проверяем диапазон
		if num < 1 || num > 4 {
			fmt.Printf("Ошибка: номер теста %d должен быть от 1 до 4\n", num)
			printUsage()
			return []int{}
		}

		numbers = append(numbers, num)
	}

	return numbers
}

// printUsage выводит справку по использованию
func printUsage() {
	fmt.Println("\nИспользование:")
	fmt.Println("  go run main.go                    # Запустить все тесты")
	fmt.Println("  go run main.go 1                  # Запустить только тест 1")
	fmt.Println("  go run main.go 1,3                # Запустить тесты 1 и 3")
	fmt.Println("  go run main.go 2,3,4              # Запустить тесты 2, 3 и 4")
	fmt.Println("  go run main.go help               # Показать эту справку")
	fmt.Println("\nДоступные тесты:")
	fmt.Println("  1 - Использование go-json (по умолчанию)")
	fmt.Println("  2 - Использование стандартного encoding/json")
	fmt.Println("  3 - Использование кастомной функции сериализации")
	fmt.Println("  4 - ID всегда первое поле")
	os.Exit(0)
}

// runAllTests запускает все тесты
func runAllTests() {
	testDefaultJSON()
	testStandardJSON()
	testCustomJSON()
	testIDFirstField()
}

// runSelectedTests запускает только выбранные тесты
func runSelectedTests(numbers []int) {
	for _, num := range numbers {
		switch num {
		case 1:
			fmt.Println("1. Использование go-json (по умолчанию)")
			testDefaultJSON()
		case 2:
			fmt.Println("2. Использование стандартного encoding/json")
			testStandardJSON()
		case 3:
			fmt.Println("3. Использование кастомной функции сериализации")
			testCustomJSON()
		case 4:
			fmt.Println("4. ID всегда первое поле")
			testIDFirstField()
		}
	}
}

func testDefaultJSON() {
	// Создаем опции инициализации с go-json (по умолчанию)
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 1000,
		CacheTTL:  10 * time.Second,
		DBFolder:  "./data/test-linedb-custom-json/default",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  []string{"id", "username"},
			},
		},
	}

	// Создаем базу данных
	db := linedb.NewLineDb(nil)

	// Инициализируем базу данных
	if err := db.Init(true, initOptions); err != nil {
		log.Printf("Failed to init database: %v", err)
		return
	}
	defer db.Close()

	// Создаем пользователя
	user := map[string]any{
		"id":        1,
		"username":  "default_user",
		"email":     "default@example.com",
		"isActive":  true,
		"role":      "user",
		"createdAt": time.Now().Unix(),
	}

	// Вставляем данные
	if err := db.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert user: %v", err)
		return
	}

	// Читаем данные
	result, err := db.Read("users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read users: %v", err)
		return
	}

	fmt.Printf("   Пользователей в базе: %d\n", len(result))
	if len(result) > 0 {
		if record, ok := result[0].(map[string]any); ok {
			fmt.Printf("   ID: %v, Username: %s\n", record["id"], record["username"])
		}
	}
}

func testStandardJSON() {
	// Создаем функции сериализации с использованием стандартного encoding/json
	standardJSONMarshal := func(v any) ([]byte, error) {
		return json.Marshal(v)
	}

	standardJSONUnmarshal := func(data []byte, v any) error {
		return json.Unmarshal(data, v)
	}

	// Создаем опции инициализации со стандартным JSON
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 1000,
		CacheTTL:  10 * time.Second,
		DBFolder:  "./data/test-linedb-custom-json/standard",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  []string{"id", "username"},
				JSONMarshal:    standardJSONMarshal,
				JSONUnmarshal:  standardJSONUnmarshal,
			},
		},
	}

	// Создаем базу данных
	db := linedb.NewLineDb(nil)

	// Инициализируем базу данных
	if err := db.Init(true, initOptions); err != nil {
		log.Printf("Failed to init database: %v", err)
		return
	}
	defer db.Close()

	// Создаем пользователя
	user := map[string]any{
		"id":        2,
		"username":  "standard_user",
		"email":     "standard@example.com",
		"isActive":  true,
		"role":      "admin",
		"createdAt": time.Now().Unix(),
	}

	// Вставляем данные
	if err := db.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert user: %v", err)
		return
	}

	// Читаем данные
	result, err := db.Read("users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read users: %v", err)
		return
	}

	fmt.Printf("   Пользователей в базе: %d\n", len(result))
	if len(result) > 0 {
		if record, ok := result[0].(map[string]any); ok {
			fmt.Printf("   ID: %v, Username: %s\n", record["id"], record["username"])
		}
	}
}

func testCustomJSON() {
	// Создаем кастомные функции сериализации с дополнительной логикой
	customJSONMarshal := func(v any) ([]byte, error) {
		// Добавляем метаданные к сериализации
		data := map[string]any{
			"data":      v,
			"timestamp": time.Now().Unix(),
			"version":   "1.0",
		}
		return json.Marshal(data)
	}

	customJSONUnmarshal := func(data []byte, v any) error {
		// Извлекаем данные из кастомного формата
		var wrapper map[string]any
		if err := json.Unmarshal(data, &wrapper); err != nil {
			return err
		}

		// Извлекаем основную часть данных
		if dataField, exists := wrapper["data"]; exists {
			// Сериализуем обратно в JSON и десериализуем в целевой тип
			dataBytes, err := json.Marshal(dataField)
			if err != nil {
				return err
			}
			return json.Unmarshal(dataBytes, v)
		}

		// Если нет поля data, пытаемся десериализовать как обычный JSON
		return json.Unmarshal(data, v)
	}

	// Создаем опции инициализации с кастомными функциями
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 1000,
		CacheTTL:  10 * time.Second,
		DBFolder:  "./data/test-linedb-custom-json/custom",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  []string{"id", "username"},
				JSONMarshal:    customJSONMarshal,
				JSONUnmarshal:  customJSONUnmarshal,
			},
			{
				CollectionName: "products",
				AllocSize:      256,
				IndexedFields:  []string{"id", "name"},
				JSONMarshal:    customJSONMarshal,
				JSONUnmarshal:  customJSONUnmarshal,
			},
		},
	}

	// Создаем базу данных
	db := linedb.NewLineDb(nil)

	// Инициализируем базу данных
	if err := db.Init(true, initOptions); err != nil {
		log.Printf("Failed to init database: %v", err)
		return
	}
	defer db.Close()

	// Создаем пользователя
	user := map[string]any{
		"id":        3,
		"username":  "custom_user",
		"email":     "custom@example.com",
		"isActive":  true,
		"role":      "moderator",
		"createdAt": time.Now().Unix(),
	}

	// Создаем продукт
	product := map[string]any{
		"id":        1,
		"name":      "Custom Product",
		"price":     99.99,
		"category":  "Electronics",
		"inStock":   true,
		"sellerId":  1,
		"createdAt": time.Now().Unix(),
	}

	// Вставляем данные
	if err := db.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert user: %v", err)
		return
	}

	if err := db.Insert(product, "products", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert product: %v", err)
		return
	}

	// Читаем данные
	users, err := db.Read("users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read users: %v", err)
		return
	}

	products, err := db.Read("products", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read products: %v", err)
		return
	}

	fmt.Printf("   Пользователей в базе: %d\n", len(users))
	fmt.Printf("   Продуктов в базе: %d\n", len(products))

	if len(users) > 0 {
		if record, ok := users[0].(map[string]any); ok {
			fmt.Printf("   Пользователь ID: %v, Username: %s\n", record["id"], record["username"])
		}
	}

	if len(products) > 0 {
		if record, ok := products[0].(map[string]any); ok {
			fmt.Printf("   Продукт ID: %v, Name: %s, Price: %v\n", record["id"], record["name"], record["price"])
		}
	}
}

func testIDFirstField() {
	// Создаем функции сериализации где id всегда первое поле
	idFirstJSONMarshal := func(v any) ([]byte, error) {
		if data, ok := v.(map[string]any); ok {
			var parts []string

			// Сначала добавляем id если он есть
			if id, exists := data["id"]; exists {
				idBytes, err := json.Marshal(id)
				if err != nil {
					return nil, err
				}
				parts = append(parts, fmt.Sprintf(`"id":%s`, string(idBytes)))
			}

			// Затем добавляем все остальные поля в алфавитном порядке
			var keys []string
			for key := range data {
				if key != "id" {
					keys = append(keys, key)
				}
			}
			sort.Strings(keys)

			for _, key := range keys {
				valueBytes, err := json.Marshal(data[key])
				if err != nil {
					return nil, err
				}
				parts = append(parts, fmt.Sprintf(`"%s":%s`, key, string(valueBytes)))
			}

			jsonStr := "{" + strings.Join(parts, ",") + "}"
			return []byte(jsonStr), nil
		}

		// Если это не map, используем стандартную сериализацию
		return json.Marshal(v)
	}

	idFirstJSONUnmarshal := func(data []byte, v any) error {
		return json.Unmarshal(data, v)
	}

	// Создаем опции инициализации с функциями id-first
	initOptions := &linedb.LineDbInitOptions{
		CacheSize: 1000,
		CacheTTL:  10 * time.Second,
		DBFolder:  "./data/test-linedb-custom-json/id-first",
		Collections: []linedb.JSONLFileOptions{
			{
				CollectionName: "users",
				AllocSize:      256,
				IndexedFields:  []string{"id", "username"},
				JSONMarshal:    idFirstJSONMarshal,
				JSONUnmarshal:  idFirstJSONUnmarshal,
			},
			{
				CollectionName: "products",
				AllocSize:      256,
				IndexedFields:  []string{"id", "name"},
				JSONMarshal:    idFirstJSONMarshal,
				JSONUnmarshal:  idFirstJSONUnmarshal,
			},
		},
	}

	// Создаем базу данных
	db := linedb.NewLineDb(nil)

	// Инициализируем базу данных
	if err := db.Init(true, initOptions); err != nil {
		log.Printf("Failed to init database: %v", err)
		return
	}
	defer db.Close()

	// Создаем пользователя (id не первое поле в исходных данных)
	user := map[string]any{
		"username":  "id_first_user",
		"email":     "id_first@example.com",
		"id":        1,
		"isActive":  true,
		"role":      "user",
		"createdAt": time.Now().Unix(),
	}

	// Создаем продукт (id не первое поле в исходных данных)
	product := map[string]any{
		"name":      "ID First Product",
		"price":     123.45,
		"id":        2,
		"category":  "Books",
		"inStock":   false,
		"sellerId":  1,
		"createdAt": time.Now().Unix(),
	}

	// Вставляем данные
	if err := db.Insert(user, "users", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert user: %v", err)
		return
	}

	if err := db.Insert(product, "products", linedb.LineDbAdapterOptions{}); err != nil {
		log.Printf("Failed to insert product: %v", err)
		return
	}

	// Читаем данные
	users, err := db.Read("users", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read users: %v", err)
		return
	}

	products, err := db.Read("products", linedb.LineDbAdapterOptions{})
	if err != nil {
		log.Printf("Failed to read products: %v", err)
		return
	}

	fmt.Printf("   Пользователей в базе: %d\n", len(users))
	fmt.Printf("   Продуктов в базе: %d\n", len(products))

	if len(users) > 0 {
		if record, ok := users[0].(map[string]any); ok {
			fmt.Printf("   Пользователь ID: %v, Username: %s\n", record["id"], record["username"])
		}
	}

	if len(products) > 0 {
		if record, ok := products[0].(map[string]any); ok {
			fmt.Printf("   Продукт ID: %v, Name: %s, Price: %v\n", record["id"], record["name"], record["price"])
		}
	}

	// Показываем содержимое файла для демонстрации
	fmt.Println("   Проверьте файл ./data/test-linedb-custom-json/id-first/users.jsonl")
	fmt.Println("   Поле 'id' должно быть первым в JSON строке")
}
