package cache

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
	"github.com/tdvorak/seen/backend/internal/config"
	"go.uber.org/zap"
)

// Manager provides centralized cache management
type Manager struct {
	client   *redis.Client
	service  *Service
	session  *SessionCache
	catalog  *CatalogCache
	download *DownloadCache
	log      *zap.Logger
}

// NewManager creates a new cache manager with all cache services
func NewManager(ctx context.Context, cfg config.CacheConfig, log *zap.Logger) (*Manager, error) {
	client, err := NewClient(ctx, cfg, log)
	if err != nil {
		return nil, fmt.Errorf("failed to create cache client: %w", err)
	}

	service := NewService(client)
	namespace := "seen"

	manager := &Manager{
		client:   client,
		service:  service,
		session:  NewSessionCache(service, namespace),
		catalog:  NewCatalogCache(service, namespace),
		download: NewDownloadCache(service, namespace),
		log:      log,
	}

	// Start background cleanup tasks
	go manager.startCleanupTasks(ctx)

	return manager, nil
}

// Client returns the underlying Redis client
func (m *Manager) Client() *redis.Client {
	return m.client
}

// Service returns the cache service
func (m *Manager) Service() *Service {
	return m.service
}

// Session returns the session cache
func (m *Manager) Session() *SessionCache {
	return m.session
}

// Catalog returns the catalog cache
func (m *Manager) Catalog() *CatalogCache {
	return m.catalog
}

// Download returns the download cache
func (m *Manager) Download() *DownloadCache {
	return m.download
}

// Ping checks if the cache is responsive
func (m *Manager) Ping(ctx context.Context) error {
	return m.service.Ping(ctx)
}

// Close closes all cache connections
func (m *Manager) Close() error {
	m.log.Info("closing cache connections")
	return m.service.Close()
}

// Stats returns cache statistics
func (m *Manager) Stats(ctx context.Context) (map[string]interface{}, error) {
	info, err := m.client.Info(ctx, "stats", "memory", "keyspace").Result()
	if err != nil {
		return nil, fmt.Errorf("failed to get cache stats: %w", err)
	}

	dbSize, err := m.client.DBSize(ctx).Result()
	if err != nil {
		return nil, fmt.Errorf("failed to get db size: %w", err)
	}

	stats := map[string]interface{}{
		"dbSize": dbSize,
		"info":   info,
	}

	return stats, nil
}

// FlushAll clears all cache data (use with extreme caution!)
func (m *Manager) FlushAll(ctx context.Context) error {
	m.log.Warn("flushing all cache data")
	return m.service.FlushDB(ctx)
}

// startCleanupTasks starts background tasks for cache maintenance
func (m *Manager) startCleanupTasks(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Minute)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			m.log.Info("stopping cache cleanup tasks")
			return
		case <-ticker.C:
			m.runCleanupTasks(ctx)
		}
	}
}

// runCleanupTasks performs periodic cache maintenance
func (m *Manager) runCleanupTasks(ctx context.Context) {
	// Cleanup stale download progress (older than 1 hour)
	if err := m.download.CleanupStaleProgress(ctx, 1*time.Hour); err != nil {
		m.log.Error("failed to cleanup stale download progress", zap.Error(err))
	}

	// Log cache stats
	stats, err := m.Stats(ctx)
	if err != nil {
		m.log.Error("failed to get cache stats", zap.Error(err))
		return
	}

	if dbSize, ok := stats["dbSize"].(int64); ok {
		m.log.Debug("cache stats", zap.Int64("keys", dbSize))
	}
}

// InvalidateUser removes all cached data for a user
func (m *Manager) InvalidateUser(ctx context.Context, userID string) error {
	m.log.Info("invalidating user cache", zap.String("userId", userID))

	// Invalidate user data
	if err := m.session.DeleteUser(ctx, userID); err != nil {
		m.log.Error("failed to delete user cache", zap.Error(err))
	}

	// Invalidate user sessions
	if err := m.session.InvalidateUserSessions(ctx, userID); err != nil {
		m.log.Error("failed to invalidate user sessions", zap.Error(err))
	}

	// Invalidate catalog data
	if err := m.catalog.InvalidateUserCatalog(ctx, userID); err != nil {
		m.log.Error("failed to invalidate user catalog", zap.Error(err))
	}

	// Invalidate download data
	if err := m.download.InvalidateUserDownloads(ctx, userID); err != nil {
		m.log.Error("failed to invalidate user downloads", zap.Error(err))
	}

	return nil
}

// WarmupCache pre-populates cache with common data
func (m *Manager) WarmupCache(ctx context.Context) error {
	m.log.Info("warming up cache")

	// Add warmup logic here as needed
	// For example, pre-cache popular catalog sections

	return nil
}

// HealthCheck performs a comprehensive health check
func (m *Manager) HealthCheck(ctx context.Context) error {
	// Check basic connectivity
	if err := m.Ping(ctx); err != nil {
		return fmt.Errorf("ping failed: %w", err)
	}

	// Test write operation
	testKey := "health:check"
	testValue := map[string]interface{}{
		"timestamp": time.Now().UTC(),
		"test":      true,
	}

	if err := m.service.Set(ctx, testKey, testValue, 10*time.Second); err != nil {
		return fmt.Errorf("write test failed: %w", err)
	}

	// Test read operation
	var readValue map[string]interface{}
	if err := m.service.Get(ctx, testKey, &readValue); err != nil {
		return fmt.Errorf("read test failed: %w", err)
	}

	// Cleanup test key
	if err := m.service.Delete(ctx, testKey); err != nil {
		m.log.Warn("failed to cleanup health check key", zap.Error(err))
	}

	return nil
}

// GetKeysByPattern retrieves all keys matching a pattern
func (m *Manager) GetKeysByPattern(ctx context.Context, pattern string) ([]string, error) {
	return m.service.Keys(ctx, pattern)
}

// DeleteKeysByPattern deletes all keys matching a pattern
func (m *Manager) DeleteKeysByPattern(ctx context.Context, pattern string) error {
	keys, err := m.service.Keys(ctx, pattern)
	if err != nil {
		return err
	}

	if len(keys) == 0 {
		return nil
	}

	m.log.Info("deleting keys by pattern",
		zap.String("pattern", pattern),
		zap.Int("count", len(keys)))

	return m.service.Delete(ctx, keys...)
}
