package linguistics

import (
	"context"
	"fmt"
	"github.com/hashicorp/golang-lru/v2"
	"sync"
	"tercul/internal/platform/cache"
	"tercul/internal/platform/config"
	"tercul/internal/platform/log"
	"time"
)

// AnalysisCache defines the interface for caching analysis results
type AnalysisCache interface {
	// Get retrieves cached analysis result
	Get(ctx context.Context, key string) (*AnalysisResult, error)

	// Set stores analysis result in cache
	Set(ctx context.Context, key string, result *AnalysisResult) error

	// IsEnabled returns whether caching is enabled
	IsEnabled() bool
}

// MemoryAnalysisCache implements in-memory caching for analysis results
type MemoryAnalysisCache struct {
	cache   *lru.Cache[string, *AnalysisResult]
	mutex   sync.RWMutex
	enabled bool
}

// NewMemoryAnalysisCache creates a new MemoryAnalysisCache
func NewMemoryAnalysisCache(cfg *config.Config, enabled bool) *MemoryAnalysisCache {
	cap := cfg.NLPMemoryCacheCap
	if cap <= 0 {
		cap = 1024
	}
	l, _ := lru.New[string, *AnalysisResult](cap)
	return &MemoryAnalysisCache{
		cache:   l,
		enabled: enabled,
	}
}

// Get retrieves cached analysis result from memory
func (c *MemoryAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) {
	if !c.enabled {
		return nil, fmt.Errorf("cache disabled")
	}

	c.mutex.RLock()
	defer c.mutex.RUnlock()

	if result, exists := c.cache.Get(key); exists {
		return result, nil
	}

	return nil, fmt.Errorf("cache miss")
}

// Set stores analysis result in memory cache
func (c *MemoryAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error {
	if !c.enabled {
		return nil
	}

	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.cache.Add(key, result)
	return nil
}

// IsEnabled returns whether caching is enabled
func (c *MemoryAnalysisCache) IsEnabled() bool {
	return c.enabled
}

// RedisAnalysisCache implements Redis-based caching for analysis results
type RedisAnalysisCache struct {
	cache   cache.Cache
	enabled bool
	ttl     time.Duration
}

// NewRedisAnalysisCache creates a new RedisAnalysisCache
func NewRedisAnalysisCache(cfg *config.Config, cache cache.Cache, enabled bool) *RedisAnalysisCache {
	ttlSeconds := cfg.NLPRedisCacheTTLSeconds
	if ttlSeconds <= 0 {
		ttlSeconds = 3600 // default 1 hour
	}
	return &RedisAnalysisCache{
		cache:   cache,
		enabled: enabled,
		ttl:     time.Duration(ttlSeconds) * time.Second,
	}
}

// Get retrieves cached analysis result from Redis
func (c *RedisAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) {
	if !c.enabled || c.cache == nil {
		return nil, fmt.Errorf("cache disabled or unavailable")
	}

	var result AnalysisResult
	err := c.cache.Get(ctx, key, &result)
	if err != nil {
		return nil, fmt.Errorf("cache miss: %w", err)
	}

	return &result, nil
}

// Set stores analysis result in Redis cache
func (c *RedisAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error {
	if !c.enabled || c.cache == nil {
		return nil
	}

	err := c.cache.Set(ctx, key, result, c.ttl)
	if err != nil {
		log.FromContext(ctx).With("key", key).Error(err, "Failed to cache analysis result")
		return err
	}

	return nil
}

// IsEnabled returns whether caching is enabled
func (c *RedisAnalysisCache) IsEnabled() bool {
	return c.enabled && c.cache != nil
}

// CompositeAnalysisCache combines multiple cache layers
type CompositeAnalysisCache struct {
	memoryCache AnalysisCache
	redisCache  AnalysisCache
	enabled     bool
}

// NewCompositeAnalysisCache creates a new CompositeAnalysisCache
func NewCompositeAnalysisCache(memoryCache AnalysisCache, redisCache AnalysisCache, enabled bool) *CompositeAnalysisCache {
	return &CompositeAnalysisCache{
		memoryCache: memoryCache,
		redisCache:  redisCache,
		enabled:     enabled,
	}
}

// Get retrieves cached analysis result from memory first, then Redis
func (c *CompositeAnalysisCache) Get(ctx context.Context, key string) (*AnalysisResult, error) {
	if !c.enabled {
		return nil, fmt.Errorf("cache disabled")
	}

	// Try memory cache first
	if result, err := c.memoryCache.Get(ctx, key); err == nil {
		return result, nil
	}

	// Try Redis cache
	if result, err := c.redisCache.Get(ctx, key); err == nil {
		// Populate memory cache with Redis result
		if err := c.memoryCache.Set(ctx, key, result); err != nil {
			log.FromContext(ctx).Warn(fmt.Sprintf("Failed to populate memory cache from Redis for key %s: %v", key, err))
		}
		return result, nil
	}

	return nil, fmt.Errorf("cache miss")
}

// Set stores analysis result in both memory and Redis caches
func (c *CompositeAnalysisCache) Set(ctx context.Context, key string, result *AnalysisResult) error {
	if !c.enabled {
		return nil
	}

	// Set in memory cache
	if err := c.memoryCache.Set(ctx, key, result); err != nil {
		log.FromContext(ctx).With("key", key).Error(err, "Failed to set memory cache")
	}

	// Set in Redis cache
	if err := c.redisCache.Set(ctx, key, result); err != nil {
		log.FromContext(ctx).With("key", key).Error(err, "Failed to set Redis cache")
		return err
	}

	return nil
}

// IsEnabled returns whether caching is enabled
func (c *CompositeAnalysisCache) IsEnabled() bool {
	return c.enabled
}