package services

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"
	"time"
)

// CacheService provides a simple in-memory cache with TTL
type CacheService struct {
	mu    sync.RWMutex
	items map[string]*cacheItem
}

type cacheItem struct {
	Value      interface{}
	Expiration time.Time
}

var (
	defaultCache     *CacheService
	cacheInitOnce    sync.Once
)

// GetCacheService returns singleton cache instance
func GetCacheService() *CacheService {
	cacheInitOnce.Do(func() {
		defaultCache = &CacheService{
			items: make(map[string]*cacheItem),
		}
		// Start cleanup goroutine
		go defaultCache.startCleanup()
	})
	return defaultCache
}

// Get retrieves value from cache
func (cs *CacheService) Get(key string, dest interface{}) error {
	cs.mu.RLock()
	item, exists := cs.items[key]
	cs.mu.RUnlock()

	if !exists {
		return fmt.Errorf("key not found")
	}

	if time.Now().After(item.Expiration) {
		cs.Delete(key)
		return fmt.Errorf("key expired")
	}

	// Type assertion or JSON marshal/unmarshal
	switch v := item.Value.(type) {
	case []byte:
		return json.Unmarshal(v, dest)
	default:
		// Marshal and unmarshal for type conversion
		data, err := json.Marshal(item.Value)
		if err != nil {
			return err
		}
		return json.Unmarshal(data, dest)
	}
}

// Set stores value in cache with TTL
func (cs *CacheService) Set(key string, value interface{}, ttl time.Duration) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	cs.items[key] = &cacheItem{
		Value:      value,
		Expiration: time.Now().Add(ttl),
	}

	return nil
}

// Delete removes item from cache
func (cs *CacheService) Delete(key string) {
	cs.mu.Lock()
	defer cs.mu.Unlock()
	delete(cs.items, key)
}

// Clear removes all items from cache
func (cs *CacheService) Clear() {
	cs.mu.Lock()
	defer cs.mu.Unlock()
	cs.items = make(map[string]*cacheItem)
}

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

	if !exists {
		return false
	}

	if time.Now().After(item.Expiration) {
		cs.Delete(key)
		return false
	}

	return true
}

// GetOrSet retrieves from cache or executes function and caches result
func (cs *CacheService) GetOrSet(key string, dest interface{}, ttl time.Duration, fn func() (interface{}, error)) error {
	// Try to get from cache
	err := cs.Get(key, dest)
	if err == nil {
		return nil
	}

	// Execute function
	value, err := fn()
	if err != nil {
		return err
	}

	// Store in cache
	if err := cs.Set(key, value, ttl); err != nil {
		return err
	}

	// Marshal and unmarshal for type conversion
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, dest)
}

// startCleanup periodically removes expired items
func (cs *CacheService) startCleanup() {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		cs.cleanup()
	}
}

func (cs *CacheService) cleanup() {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	now := time.Now()
	for key, item := range cs.items {
		if now.After(item.Expiration) {
			delete(cs.items, key)
		}
	}
}

// Stats returns cache statistics
func (cs *CacheService) Stats() map[string]interface{} {
	cs.mu.RLock()
	defer cs.mu.RUnlock()

	expired := 0
	now := time.Now()
	for _, item := range cs.items {
		if now.After(item.Expiration) {
			expired++
		}
	}

	return map[string]interface{}{
		"total_items":   len(cs.items),
		"expired_items": expired,
		"active_items":  len(cs.items) - expired,
	}
}

// CacheKey generates consistent cache keys
func CacheKey(parts ...string) string {
	return "cache:" + joinStrings(parts, ":")
}

func joinStrings(parts []string, sep string) string {
	if len(parts) == 0 {
		return ""
	}
	result := parts[0]
	for i := 1; i < len(parts); i++ {
		result += sep + parts[i]
	}
	return result
}

// WithCache is a decorator for caching function results
func WithCache(key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) {
	cache := GetCacheService()

	// Try cache first
	var result interface{}
	if err := cache.Get(key, &result); err == nil {
		return result, nil
	}

	// Execute function
	result, err := fn()
	if err != nil {
		return nil, err
	}

	// Cache result
	cache.Set(key, result, ttl)

	return result, nil
}

// InvalidateCachePattern removes all keys matching pattern
func (cs *CacheService) InvalidateCachePattern(pattern string) {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	for key := range cs.items {
		if matchesPattern(key, pattern) {
			delete(cs.items, key)
		}
	}
}

func matchesPattern(key, pattern string) bool {
	// Simple pattern matching - supports * wildcard
	if pattern == "*" {
		return true
	}

	// Check if pattern contains wildcard
	if len(pattern) > 0 && pattern[len(pattern)-1] == '*' {
		prefix := pattern[:len(pattern)-1]
		return len(key) >= len(prefix) && key[:len(prefix)] == prefix
	}

	return key == pattern
}

// WarmupCache preloads frequently accessed data
func WarmupCache(ctx context.Context) error {
	_ = GetCacheService() // Available for warmup logic

	// Example: preload settings
	// This should be called on application startup
	
	// Add your warmup logic here
	// Example:
	// cache := GetCacheService()
	// settings, err := fetchSettings()
	// if err == nil {
	//     cache.Set("settings:main", settings, 1*time.Hour)
	// }

	return nil
}
