package cache

import (
	"context"
	"encoding/json"
	"time"

	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/config"
	"github.com/kms/api-key-service/internal/errors"
)

// CacheProvider defines the interface for cache operations
type CacheProvider interface {
	// Get retrieves a value from cache
	Get(ctx context.Context, key string) ([]byte, error)
	
	// Set stores a value in cache with TTL
	Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
	
	// Delete removes a value from cache
	Delete(ctx context.Context, key string) error
	
	// Exists checks if a key exists in cache
	Exists(ctx context.Context, key string) (bool, error)
	
	// Clear removes all cached values (use with caution)
	Clear(ctx context.Context) error
	
	// Close closes the cache connection
	Close() error
}

// MemoryCache implements CacheProvider using in-memory storage
type MemoryCache struct {
	data   map[string]cacheItem
	config config.ConfigProvider
	logger *zap.Logger
}

type cacheItem struct {
	Value     []byte
	ExpiresAt time.Time
}

// NewMemoryCache creates a new in-memory cache
func NewMemoryCache(config config.ConfigProvider, logger *zap.Logger) CacheProvider {
	cache := &MemoryCache{
		data:   make(map[string]cacheItem),
		config: config,
		logger: logger,
	}
	
	// Start cleanup goroutine
	go cache.cleanup()
	
	return cache
}

// Get retrieves a value from memory cache
func (m *MemoryCache) Get(ctx context.Context, key string) ([]byte, error) {
	m.logger.Debug("Getting value from memory cache", zap.String("key", key))
	
	item, exists := m.data[key]
	if !exists {
		return nil, errors.NewNotFoundError("cache key")
	}
	
	// Check if expired
	if time.Now().After(item.ExpiresAt) {
		delete(m.data, key)
		return nil, errors.NewNotFoundError("cache key")
	}
	
	return item.Value, nil
}

// Set stores a value in memory cache
func (m *MemoryCache) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
	m.logger.Debug("Setting value in memory cache", 
		zap.String("key", key), 
		zap.Duration("ttl", ttl))
	
	m.data[key] = cacheItem{
		Value:     value,
		ExpiresAt: time.Now().Add(ttl),
	}
	
	return nil
}

// Delete removes a value from memory cache
func (m *MemoryCache) Delete(ctx context.Context, key string) error {
	m.logger.Debug("Deleting value from memory cache", zap.String("key", key))
	
	delete(m.data, key)
	return nil
}

// Exists checks if a key exists in memory cache
func (m *MemoryCache) Exists(ctx context.Context, key string) (bool, error) {
	item, exists := m.data[key]
	if !exists {
		return false, nil
	}
	
	// Check if expired
	if time.Now().After(item.ExpiresAt) {
		delete(m.data, key)
		return false, nil
	}
	
	return true, nil
}

// Clear removes all values from memory cache
func (m *MemoryCache) Clear(ctx context.Context) error {
	m.logger.Debug("Clearing memory cache")
	
	m.data = make(map[string]cacheItem)
	return nil
}

// Close closes the memory cache (no-op for memory cache)
func (m *MemoryCache) Close() error {
	return nil
}

// cleanup removes expired items from memory cache
func (m *MemoryCache) cleanup() {
	ticker := time.NewTicker(5 * time.Minute) // Cleanup every 5 minutes
	defer ticker.Stop()
	
	for range ticker.C {
		now := time.Now()
		for key, item := range m.data {
			if now.After(item.ExpiresAt) {
				delete(m.data, key)
			}
		}
	}
}

// CacheManager provides high-level caching operations with JSON serialization
type CacheManager struct {
	provider CacheProvider
	config   config.ConfigProvider
	logger   *zap.Logger
}

// NewCacheManager creates a new cache manager
func NewCacheManager(config config.ConfigProvider, logger *zap.Logger) *CacheManager {
	var provider CacheProvider
	
	// Use Redis if configured, otherwise fall back to memory cache
	if config.GetBool("REDIS_ENABLED") {
		redisProvider, err := NewRedisCache(config, logger)
		if err != nil {
			logger.Warn("Failed to initialize Redis cache, falling back to memory cache", zap.Error(err))
			provider = NewMemoryCache(config, logger)
		} else {
			provider = redisProvider
		}
	} else {
		provider = NewMemoryCache(config, logger)
	}
	
	return &CacheManager{
		provider: provider,
		config:   config,
		logger:   logger,
	}
}

// GetJSON retrieves and unmarshals a JSON value from cache
func (c *CacheManager) GetJSON(ctx context.Context, key string, dest interface{}) error {
	c.logger.Debug("Getting JSON from cache", zap.String("key", key))
	
	data, err := c.provider.Get(ctx, key)
	if err != nil {
		return err
	}
	
	if err := json.Unmarshal(data, dest); err != nil {
		c.logger.Error("Failed to unmarshal cached JSON", zap.Error(err))
		return errors.NewInternalError("Failed to unmarshal cached data").WithInternal(err)
	}
	
	return nil
}

// SetJSON marshals and stores a JSON value in cache
func (c *CacheManager) SetJSON(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
	c.logger.Debug("Setting JSON in cache", 
		zap.String("key", key), 
		zap.Duration("ttl", ttl))
	
	data, err := json.Marshal(value)
	if err != nil {
		c.logger.Error("Failed to marshal JSON for cache", zap.Error(err))
		return errors.NewInternalError("Failed to marshal data for cache").WithInternal(err)
	}
	
	return c.provider.Set(ctx, key, data, ttl)
}

// Get retrieves raw bytes from cache
func (c *CacheManager) Get(ctx context.Context, key string) ([]byte, error) {
	return c.provider.Get(ctx, key)
}

// Set stores raw bytes in cache
func (c *CacheManager) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
	return c.provider.Set(ctx, key, value, ttl)
}

// Delete removes a value from cache
func (c *CacheManager) Delete(ctx context.Context, key string) error {
	return c.provider.Delete(ctx, key)
}

// Exists checks if a key exists in cache
func (c *CacheManager) Exists(ctx context.Context, key string) (bool, error) {
	return c.provider.Exists(ctx, key)
}

// Clear removes all cached values
func (c *CacheManager) Clear(ctx context.Context) error {
	return c.provider.Clear(ctx)
}

// Close closes the cache connection
func (c *CacheManager) Close() error {
	return c.provider.Close()
}

// GetDefaultTTL returns the default TTL from config
func (c *CacheManager) GetDefaultTTL() time.Duration {
	return c.config.GetDuration("CACHE_TTL")
}

// IsEnabled returns whether caching is enabled
func (c *CacheManager) IsEnabled() bool {
	return c.config.GetBool("CACHE_ENABLED")
}

// CacheKey generates a cache key with prefix
func CacheKey(prefix, key string) string {
	return prefix + ":" + key
}

// Common cache key prefixes
const (
	KeyPrefixPermission    = "perm"
	KeyPrefixApplication   = "app"
	KeyPrefixToken         = "token"
	KeyPrefixUserClaims    = "user_claims"
	KeyPrefixTokenRevoked  = "token_revoked"
)
