package linedb

import (
	"strings"
	"sync"
	"time"
)

// CacheEntry представляет запись в кэше
type CacheEntry struct {
	Data      any
	Timestamp time.Time
	TTL       time.Duration
}

// RecordCache представляет кэш записей
type RecordCache struct {
	cache    map[string]*CacheEntry
	mutex    sync.RWMutex
	maxSize  int
	ttl      time.Duration
	stopChan chan struct{}
}

// NewRecordCache создает новый кэш
func NewRecordCache(maxSize int, ttl time.Duration) *RecordCache {
	cache := &RecordCache{
		cache:    make(map[string]*CacheEntry),
		maxSize:  maxSize,
		ttl:      ttl,
		stopChan: make(chan struct{}),
	}

	// Запускаем очистку устаревших записей только если TTL > 0
	if ttl > 0 {
		go cache.cleanupLoop()
	}

	return cache
}

// Set устанавливает значение в кэш
func (c *RecordCache) Set(key string, value any) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	// Проверяем размер кэша
	if len(c.cache) >= c.maxSize {
		c.evictOldest()
	}

	c.cache[key] = &CacheEntry{
		Data:      value,
		Timestamp: time.Now(),
		TTL:       c.ttl,
	}
}

// Get получает значение из кэша
func (c *RecordCache) Get(key string) (any, bool) {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	entry, exists := c.cache[key]
	if !exists {
		return nil, false
	}

	// Проверяем TTL
	if c.ttl > 0 && time.Since(entry.Timestamp) > c.ttl {
		delete(c.cache, key)
		return nil, false
	}

	return entry.Data, true
}

// Has проверяет наличие ключа в кэше
func (c *RecordCache) Has(key string) bool {
	_, exists := c.Get(key)
	return exists
}

// Delete удаляет ключ из кэша
func (c *RecordCache) Delete(key string) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	delete(c.cache, key)
}

// Clear очищает кэш полностью.
func (c *RecordCache) Clear() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.cache = make(map[string]*CacheEntry)
}

// ClearCollection удаляет записи кэша, относящиеся к коллекции collectionName:
// ключи ReadByFilter ("collection:filter" и "collection_part:filter") и SetByRecord ("id:collection", "id:collection_part").
func (c *RecordCache) ClearCollection(collectionName string) {
	if collectionName == "" {
		c.Clear()
		return
	}
	c.mutex.Lock()
	defer c.mutex.Unlock()
	for key := range c.cache {
		if cacheKeyBelongsToCollection(key, collectionName) {
			delete(c.cache, key)
		}
	}
}

func cacheKeyBelongsToCollection(key, collectionName string) bool {
	if strings.HasPrefix(key, collectionName+":") {
		return true
	}
	if strings.HasPrefix(key, collectionName+"_") {
		return true
	}
	idx := strings.LastIndex(key, ":")
	if idx < 0 {
		return false
	}
	suf := key[idx+1:]
	return suf == collectionName || strings.HasPrefix(suf, collectionName+"_")
}

// ClearEntriesContainingIDs удаляет из кэша только те записи, в данных которых
// встречается хотя бы один из переданных id. Если ids пуст — ничего не делает.
func (c *RecordCache) ClearEntriesContainingIDs(ids []any) {
	if len(ids) == 0 {
		return
	}
	c.mutex.Lock()
	defer c.mutex.Unlock()
	for key, entry := range c.cache {
		if entry.Data == nil {
			continue
		}
		records, ok := entry.Data.([]any)
		if !ok {
			continue
		}
		for _, rec := range records {
			m, ok := rec.(map[string]any)
			if !ok {
				continue
			}
			recID := m["id"]
			for _, id := range ids {
				if idsEqual(recID, id) {
					delete(c.cache, key)
					goto nextKey
				}
			}
		}
	nextKey:
	}
}

func idsEqual(a, b any) bool {
	if a == b {
		return true
	}
	// JSON unmarshals numbers as float64; сравниваем численно
	if an, ok := toFloat64(a); ok {
		if bn, ok := toFloat64(b); ok {
			return an == bn
		}
	}
	return false
}

func toFloat64(v any) (float64, bool) {
	switch x := v.(type) {
	case float64:
		return x, true
	case int:
		return float64(x), true
	case int64:
		return float64(x), true
	}
	return 0, false
}

// Stop останавливает кэш
func (c *RecordCache) Stop() {
	close(c.stopChan)
}

// Size возвращает размер кэша
func (c *RecordCache) Size() int {
	c.mutex.RLock()
	defer c.mutex.RUnlock()
	return len(c.cache)
}

// GetFlatCacheMap возвращает плоскую карту кэша
func (c *RecordCache) GetFlatCacheMap() map[string]*CacheEntry {
	c.mutex.RLock()
	defer c.mutex.RUnlock()

	result := make(map[string]*CacheEntry)
	for key, entry := range c.cache {
		result[key] = entry
	}
	return result
}

// evictOldest удаляет самую старую запись из кэша
func (c *RecordCache) evictOldest() {
	var oldestKey string
	var oldestTime time.Time

	for key, entry := range c.cache {
		if oldestKey == "" || entry.Timestamp.Before(oldestTime) {
			oldestKey = key
			oldestTime = entry.Timestamp
		}
	}

	if oldestKey != "" {
		delete(c.cache, oldestKey)
	}
}

// cleanupLoop запускает цикл очистки устаревших записей
func (c *RecordCache) cleanupLoop() {
	// Используем более безопасный интервал
	interval := c.ttl / 4
	if interval < time.Second {
		interval = time.Second
	}

	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			c.cleanup()
		case <-c.stopChan:
			return
		}
	}
}

// cleanup очищает устаревшие записи
func (c *RecordCache) cleanup() {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	now := time.Now()
	for key, entry := range c.cache {
		if c.ttl > 0 && now.Sub(entry.Timestamp) > c.ttl {
			delete(c.cache, key)
		}
	}
}

// SetByRecord устанавливает запись в кэш по записи
func (c *RecordCache) SetByRecord(record any, collectionName string) {
	key := toString(record) + ":" + collectionName
	c.Set(key, record)
}

// UpdateCacheAfterInsert обновляет кэш после вставки
func (c *RecordCache) UpdateCacheAfterInsert(record any, collectionName string) {
	c.SetByRecord(record, collectionName)
}

// toString преобразует значение в строку
func toString(value any) string {
	switch v := value.(type) {
	case string:
		return v
	case int:
		return string(rune(v))
	case float64:
		return string(rune(int(v)))
	case map[string]any:
		if id, exists := v["id"]; exists {
			return toString(id)
		}
		return "unknown"
	default:
		return "unknown"
	}
}
