package linedb

import (
	"fmt"
	"strconv"
	"sync"
)

// IndexPosition — позиция записи в индексе (партиция + номер строки).
// Partition "default" — для обычных коллекций без партиционирования.
type IndexPosition struct {
	Partition string // имя партиции ("default" для обычных коллекций)
	LineIndex int
}

// DefaultPartition — значение партиции для непартиционированных коллекций.
const DefaultPartition = "default"

// IndexStore — интерфейс хранилища индексов.
// Позволяет подключать разные реализации: в памяти, memcached и др.
// Индекс привязан к логической коллекции; для партиционированных хранит (partition, lineIndex).
type IndexStore interface {
	// Lookup ищет позиции записей по полю и значению.
	// value — строковое представление (см. valueToIndexKey).
	// Возвращает (partition, lineIndex) — для непартиционированных partition = DefaultPartition.
	Lookup(collection, field, value string) ([]IndexPosition, error)

	// IndexRecord добавляет одну запись в индекс.
	// partition — имя партиции (DefaultPartition для обычных коллекций).
	IndexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int)

	// UnindexRecord удаляет одну запись из индекса.
	UnindexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int)

	// Rebuild перестраивает вклад в индекс для одной партиции (или всей коллекции, если одна партиция).
	// lineIndexes[i] — 0-based номер физической строки в файле для records[i] (после пустых/удалённых строк).
	// Если lineIndexes == nil или len != len(records), используются плотные индексы 0..len-1 (устаревшее поведение).
	Rebuild(collection, partition string, fields []string, records []any, lineIndexes []int) error

	// Clear очищает индекс коллекции (все партиции).
	Clear(collection string) error
}

// valueToIndexKey преобразует значение фильтра в ключ для индекса.
func valueToIndexKey(v any) string {
	if v == nil {
		return ""
	}
	switch t := v.(type) {
	case string:
		return t
	case int:
		return strconv.Itoa(t)
	case int64:
		return strconv.FormatInt(t, 10)
	case float64:
		return strconv.FormatFloat(t, 'g', -1, 64)
	case bool:
		if t {
			return "true"
		}
		return "false"
	default:
		return fmt.Sprintf("%v", v)
	}
}

// getFieldValue извлекает значение поля из записи и превращает в ключ индекса.
func getFieldValue(record map[string]any, field string) string {
	v, ok := record[field]
	if !ok {
		return ""
	}
	return valueToIndexKey(v)
}

// InMemoryIndexStore — реализация IndexStore в памяти (по умолчанию).
type InMemoryIndexStore struct {
	mu sync.RWMutex
	// index: collection:field -> value -> []IndexPosition
	index map[string]map[string][]IndexPosition
}

// NewInMemoryIndexStore создаёт новый in-memory индекс.
func NewInMemoryIndexStore() *InMemoryIndexStore {
	return &InMemoryIndexStore{
		index: make(map[string]map[string][]IndexPosition),
	}
}

// indexKey формирует ключ карты для collection:field.
func (s *InMemoryIndexStore) indexKey(collection, field string) string {
	return collection + ":" + field
}

// IndexRecord добавляет запись в индекс.
func (s *InMemoryIndexStore) IndexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int) {
	if len(fields) == 0 || record == nil {
		return
	}
	if partition == "" {
		partition = DefaultPartition
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	for _, field := range fields {
		val := getFieldValue(record, field)
		key := s.indexKey(collection, field)
		if s.index[key] == nil {
			s.index[key] = make(map[string][]IndexPosition)
		}
		s.index[key][val] = append(s.index[key][val], IndexPosition{Partition: partition, LineIndex: lineIndex})
	}
}

// UnindexRecord удаляет запись из индекса (по partition и lineIndex).
func (s *InMemoryIndexStore) UnindexRecord(collection, partition string, fields []string, record map[string]any, lineIndex int) {
	if len(fields) == 0 || record == nil {
		return
	}
	if partition == "" {
		partition = DefaultPartition
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	for _, field := range fields {
		val := getFieldValue(record, field)
		key := s.indexKey(collection, field)
		bucket := s.index[key]
		if bucket == nil {
			continue
		}
		positions := bucket[val]
		newPos := make([]IndexPosition, 0, len(positions))
		for _, p := range positions {
			if p.Partition != partition || p.LineIndex != lineIndex {
				newPos = append(newPos, p)
			}
		}
		if len(newPos) == 0 {
			delete(bucket, val)
		} else {
			bucket[val] = newPos
		}
	}
}

// Lookup возвращает позиции (partition, lineIndex) по полю и значению.
func (s *InMemoryIndexStore) Lookup(collection, field, value string) ([]IndexPosition, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	key := s.indexKey(collection, field)
	bucket := s.index[key]
	if bucket == nil {
		return nil, nil
	}
	positions := bucket[value]
	if len(positions) == 0 {
		return nil, nil
	}
	out := make([]IndexPosition, len(positions))
	copy(out, positions)
	return out, nil
}

// Rebuild перестраивает вклад партиции в индекс.
func (s *InMemoryIndexStore) Rebuild(collection, partition string, fields []string, records []any, lineIndexes []int) error {
	if partition == "" {
		partition = DefaultPartition
	}
	s.mu.Lock()
	defer s.mu.Unlock()

	// Удаляем старые позиции этой партиции из индекса
	for _, field := range fields {
		key := s.indexKey(collection, field)
		bucket := s.index[key]
		if bucket == nil {
			continue
		}
		for val, positions := range bucket {
			newPos := make([]IndexPosition, 0, len(positions))
			for _, p := range positions {
				if p.Partition != partition {
					newPos = append(newPos, p)
				}
			}
			if len(newPos) == 0 {
				delete(bucket, val)
			} else {
				bucket[val] = newPos
			}
		}
	}

	// Добавляем новые позиции
	for i, rec := range records {
		lineIdx := i
		if lineIndexes != nil && i < len(lineIndexes) {
			lineIdx = lineIndexes[i]
		}
		recMap, ok := rec.(map[string]any)
		if !ok {
			continue
		}
		for _, field := range fields {
			val := getFieldValue(recMap, field)
			key := s.indexKey(collection, field)
			if s.index[key] == nil {
				s.index[key] = make(map[string][]IndexPosition)
			}
			s.index[key][val] = append(s.index[key][val], IndexPosition{Partition: partition, LineIndex: lineIdx})
		}
	}
	return nil
}

// Clear очищает индекс коллекции.
func (s *InMemoryIndexStore) Clear(collection string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	prefix := collection + ":"
	for k := range s.index {
		if len(k) > len(prefix) && k[:len(prefix)] == prefix {
			delete(s.index, k)
		}
	}
	return nil
}

// GetSnapshotForTest возвращает копию индекса для тестов. Доступ только при accessKey == "give_me_cache".
// Ключ — "collection:field", значение — map[string][]IndexPosition.
func (s *InMemoryIndexStore) GetSnapshotForTest(accessKey string) map[string]any {
	const testAccessKey = "give_me_cache"
	if accessKey != testAccessKey {
		return map[string]any{}
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make(map[string]any, len(s.index))
	for k, bucket := range s.index {
		cp := make(map[string][]IndexPosition, len(bucket))
		for val, positions := range bucket {
			pos2 := make([]IndexPosition, len(positions))
			copy(pos2, positions)
			cp[val] = pos2
		}
		out[k] = cp
	}
	return out
}
