package backends

import (
	"container/list"
	"sync"
	"time"
)

// cacheShard represents a single shard of the sharded cache
// Each shard has its own lock for reduced contention
type cacheShard struct {
	items      map[string]*memoryCacheItem
	lruList    *list.List
	mu         sync.RWMutex
	maxSize    int64
	maxMemory  int64
	size       int64
	memoryUsed int64
}

// newCacheShard creates a new cache shard
func newCacheShard(maxSize, maxMemory int64) *cacheShard {
	return &cacheShard{
		items:     make(map[string]*memoryCacheItem),
		lruList:   list.New(),
		maxSize:   maxSize,
		maxMemory: maxMemory,
	}
}

// get retrieves a value from this shard
// Returns: value, exists, expired
func (s *cacheShard) get(key string) (interface{}, bool, bool) {
	s.mu.RLock()
	item, exists := s.items[key]
	s.mu.RUnlock()

	if !exists {
		return nil, false, false
	}

	if item.isExpired() {
		return nil, true, true // exists but expired
	}

	// Update access time and LRU position under write lock
	s.mu.Lock()
	// Re-check item exists (could have been deleted)
	item, exists = s.items[key]
	if exists && !item.isExpired() {
		item.accessedAt = time.Now()
		item.accessCount++
		if elem, ok := item.element.(*list.Element); ok && elem != nil {
			s.lruList.MoveToFront(elem)
		}
	}
	s.mu.Unlock()

	if !exists || item.isExpired() {
		return nil, false, false
	}

	return item.value, true, false
}

// set stores a value in this shard
func (s *cacheShard) set(key string, value interface{}, expiresAt time.Time, size int64) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Check if we need to evict items
	if s.maxSize > 0 && s.size >= s.maxSize {
		s.evictLRULocked()
	}
	if s.maxMemory > 0 && s.memoryUsed+size > s.maxMemory {
		s.evictLRULocked()
	}

	// Remove old item if exists
	if oldItem, exists := s.items[key]; exists {
		s.memoryUsed -= oldItem.size
		if elem, ok := oldItem.element.(*list.Element); ok && elem != nil {
			s.lruList.Remove(elem)
		}
		s.size--
	}

	now := time.Now()
	item := &memoryCacheItem{
		key:         key,
		value:       value,
		expiresAt:   expiresAt,
		createdAt:   now,
		accessedAt:  now,
		accessCount: 0,
		size:        size,
	}

	item.element = s.lruList.PushFront(item)
	s.items[key] = item
	s.size++
	s.memoryUsed += size
}

// delete removes a key from this shard
// Returns true if the key was deleted
func (s *cacheShard) delete(key string) bool {
	s.mu.Lock()
	defer s.mu.Unlock()

	item, exists := s.items[key]
	if !exists {
		return false
	}

	s.deleteItemLocked(item)
	return true
}

// exists checks if a key exists (and is not expired)
func (s *cacheShard) exists(key string) bool {
	s.mu.RLock()
	item, exists := s.items[key]
	s.mu.RUnlock()

	if !exists {
		return false
	}

	return !item.isExpired()
}

// ttl returns the remaining TTL for a key
func (s *cacheShard) ttl(key string) (time.Duration, bool) {
	s.mu.RLock()
	item, exists := s.items[key]
	s.mu.RUnlock()

	if !exists || item.isExpired() {
		return 0, false
	}

	if item.expiresAt.IsZero() {
		return 0, true // No expiration
	}

	remaining := time.Until(item.expiresAt)
	if remaining < 0 {
		return 0, false
	}

	return remaining, true
}

// expire updates the TTL for an existing key
func (s *cacheShard) expire(key string, ttl time.Duration) bool {
	s.mu.Lock()
	defer s.mu.Unlock()

	item, exists := s.items[key]
	if !exists || item.isExpired() {
		return false
	}

	if ttl > 0 {
		item.expiresAt = time.Now().Add(ttl)
	} else {
		item.expiresAt = time.Time{} // Remove expiration
	}

	return true
}

// keys returns all non-expired keys matching the pattern
func (s *cacheShard) keys(pattern string) []string {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var keys []string
	for key, item := range s.items {
		if !item.isExpired() && matchPattern(pattern, key) {
			keys = append(keys, key)
		}
	}
	return keys
}

// clear removes all items from this shard
func (s *cacheShard) clear() {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.items = make(map[string]*memoryCacheItem)
	s.lruList.Init()
	s.size = 0
	s.memoryUsed = 0
}

// cleanup removes expired items
// Returns the number of items removed
func (s *cacheShard) cleanup() int {
	s.mu.Lock()
	defer s.mu.Unlock()

	var toRemove []*memoryCacheItem
	for _, item := range s.items {
		if item.isExpired() {
			toRemove = append(toRemove, item)
		}
	}

	for _, item := range toRemove {
		s.deleteItemLocked(item)
	}

	return len(toRemove)
}

// stats returns statistics for this shard
func (s *cacheShard) stats() (size, memory int64) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.size, s.memoryUsed
}

// deleteItemLocked removes an item (must be called with lock held)
func (s *cacheShard) deleteItemLocked(item *memoryCacheItem) {
	if elem, ok := item.element.(*list.Element); ok && elem != nil {
		s.lruList.Remove(elem)
	}
	delete(s.items, item.key)
	s.size--
	s.memoryUsed -= item.size
}

// evictLRULocked evicts the least recently used item (must be called with lock held)
func (s *cacheShard) evictLRULocked() bool {
	if s.lruList.Len() == 0 {
		return false
	}

	element := s.lruList.Back()
	if element != nil {
		item, ok := element.Value.(*memoryCacheItem)
		if ok {
			s.deleteItemLocked(item)
			return true
		}
	}
	return false
}

// evictOne evicts one item from this shard (for global limit enforcement)
func (s *cacheShard) evictOne() bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.evictLRULocked()
}

// getOldestAccessTime returns the access time of the LRU item (oldest) in this shard
// Returns zero time if shard is empty
func (s *cacheShard) getOldestAccessTime() time.Time {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.lruList.Len() == 0 {
		return time.Time{}
	}

	element := s.lruList.Back()
	if element != nil {
		item, ok := element.Value.(*memoryCacheItem)
		if ok {
			return item.accessedAt
		}
	}
	return time.Time{}
}

// fnv32 computes FNV-1a hash of a string
// This is a fast, well-distributed hash function
func fnv32(key string) uint32 {
	const (
		offset32 = uint32(2166136261)
		prime32  = uint32(16777619)
	)

	hash := offset32
	for i := 0; i < len(key); i++ {
		hash ^= uint32(key[i])
		hash *= prime32
	}
	return hash
}
