package cache

import (
	"fmt"
	"log"
	"sync"
	"time"
)

// CacheEntry represents a cached item with expiration
type cacheEntry struct {
	data       interface{}
	expiration time.Time
}

// CVCache provides thread-safe caching for CV and UI data
type CVCache struct {
	mu      sync.RWMutex
	entries map[string]*cacheEntry
	ttl     time.Duration
	stats   CacheStats
}

// CacheStats tracks cache performance metrics
type CacheStats struct {
	mu     sync.RWMutex
	Hits   int64
	Misses int64
	Size   int
}

// New creates a new CVCache with the specified TTL
func New(ttl time.Duration) *CVCache {
	if ttl <= 0 {
		ttl = 1 * time.Hour // Default TTL
	}

	cache := &CVCache{
		entries: make(map[string]*cacheEntry),
		ttl:     ttl,
		stats:   CacheStats{},
	}

	// Start background cleanup goroutine
	go cache.cleanupExpired()

	return cache
}

// Get retrieves an item from the cache
func (c *CVCache) Get(key string) (interface{}, bool) {
	c.mu.RLock()
	entry, exists := c.entries[key]
	c.mu.RUnlock()

	if !exists {
		c.recordMiss()
		return nil, false
	}

	// Check if expired
	if time.Now().After(entry.expiration) {
		c.mu.Lock()
		delete(c.entries, key)
		c.mu.Unlock()
		c.recordMiss()
		return nil, false
	}

	c.recordHit()
	return entry.data, true
}

// Set stores an item in the cache with TTL
func (c *CVCache) Set(key string, data interface{}) {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.entries[key] = &cacheEntry{
		data:       data,
		expiration: time.Now().Add(c.ttl),
	}

	c.updateSize()
}

// Invalidate removes a specific key from cache
func (c *CVCache) Invalidate(key string) {
	c.mu.Lock()
	defer c.mu.Unlock()

	delete(c.entries, key)
	c.updateSize()
}

// InvalidateAll clears the entire cache
func (c *CVCache) InvalidateAll() {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.entries = make(map[string]*cacheEntry)
	c.updateSize()
	log.Println("🗑️  Cache invalidated")
}

// Warm preloads cache with specific keys
func (c *CVCache) Warm(key string, loader func() (interface{}, error)) error {
	data, err := loader()
	if err != nil {
		return fmt.Errorf("cache warm failed for key %s: %w", key, err)
	}

	c.Set(key, data)
	log.Printf("🔥 Cache warmed: %s", key)
	return nil
}

// GetStats returns current cache statistics
func (c *CVCache) GetStats() CacheStats {
	c.stats.mu.RLock()
	defer c.stats.mu.RUnlock()

	return CacheStats{
		Hits:   c.stats.Hits,
		Misses: c.stats.Misses,
		Size:   c.stats.Size,
	}
}

// HitRate returns the cache hit rate as a percentage
func (c *CVCache) HitRate() float64 {
	stats := c.GetStats()
	total := stats.Hits + stats.Misses
	if total == 0 {
		return 0
	}
	return (float64(stats.Hits) / float64(total)) * 100
}

// recordHit increments the hit counter
func (c *CVCache) recordHit() {
	c.stats.mu.Lock()
	c.stats.Hits++
	c.stats.mu.Unlock()
}

// recordMiss increments the miss counter
func (c *CVCache) recordMiss() {
	c.stats.mu.Lock()
	c.stats.Misses++
	c.stats.mu.Unlock()
}

// updateSize updates the cached size count
func (c *CVCache) updateSize() {
	c.stats.mu.Lock()
	c.stats.Size = len(c.entries)
	c.stats.mu.Unlock()
}

// cleanupExpired periodically removes expired entries
func (c *CVCache) cleanupExpired() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		c.mu.Lock()
		now := time.Now()
		removed := 0

		for key, entry := range c.entries {
			if now.After(entry.expiration) {
				delete(c.entries, key)
				removed++
			}
		}

		if removed > 0 {
			c.updateSize()
			log.Printf("🧹 Cache cleanup: removed %d expired entries", removed)
		}
		c.mu.Unlock()
	}
}

// BuildKey creates a consistent cache key
func BuildKey(prefix, lang string) string {
	return fmt.Sprintf("%s:%s", prefix, lang)
}
