// File Path: monorepo/cloud/maplepress-backend/pkg/cache/twotier.go
package cache

import (
	"context"
	"time"

	"go.uber.org/zap"
)

// TwoTierCacher defines the interface for two-tier cache operations
type TwoTierCacher interface {
	Shutdown(ctx context.Context)
	Get(ctx context.Context, key string) ([]byte, error)
	Set(ctx context.Context, key string, val []byte) error
	SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error
	Delete(ctx context.Context, key string) error
	PurgeExpired(ctx context.Context) error
}

// twoTierCache implements a clean 2-layer (read-through write-through) cache
//
// L1: Redis (fast, in-memory)
// L2: Cassandra (persistent)
//
// On Get: check Redis → then Cassandra → if found in Cassandra → populate Redis
// On Set: write to both
// On SetWithExpiry: write to both with expiry
// On Delete: remove from both
type twoTierCache struct {
	redisCache     RedisCacher
	cassandraCache CassandraCacher
	logger         *zap.Logger
}

// NewTwoTierCache creates a new two-tier cache instance
func NewTwoTierCache(redisCache RedisCacher, cassandraCache CassandraCacher, logger *zap.Logger) TwoTierCacher {
	logger = logger.Named("two-tier-cache")
	logger.Info("✓ Two-tier cache initialized (Redis L1 + Cassandra L2)")
	return &twoTierCache{
		redisCache:     redisCache,
		cassandraCache: cassandraCache,
		logger:         logger,
	}
}

func (c *twoTierCache) Get(ctx context.Context, key string) ([]byte, error) {
	// Try L1 (Redis) first
	val, err := c.redisCache.Get(ctx, key)
	if err != nil {
		return nil, err
	}
	if val != nil {
		c.logger.Debug("cache hit from Redis", zap.String("key", key))
		return val, nil
	}

	// Not in Redis, try L2 (Cassandra)
	val, err = c.cassandraCache.Get(ctx, key)
	if err != nil {
		return nil, err
	}
	if val != nil {
		// Found in Cassandra, populate Redis for future lookups
		c.logger.Debug("cache hit from Cassandra, writing back to Redis", zap.String("key", key))
		_ = c.redisCache.Set(ctx, key, val) // Best effort, don't fail if Redis write fails
	}
	return val, nil
}

func (c *twoTierCache) Set(ctx context.Context, key string, val []byte) error {
	// Write to both layers
	if err := c.redisCache.Set(ctx, key, val); err != nil {
		return err
	}
	if err := c.cassandraCache.Set(ctx, key, val); err != nil {
		return err
	}
	return nil
}

func (c *twoTierCache) SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error {
	// Write to both layers with expiry
	if err := c.redisCache.SetWithExpiry(ctx, key, val, expiry); err != nil {
		return err
	}
	if err := c.cassandraCache.SetWithExpiry(ctx, key, val, expiry); err != nil {
		return err
	}
	return nil
}

func (c *twoTierCache) Delete(ctx context.Context, key string) error {
	// Remove from both layers
	if err := c.redisCache.Delete(ctx, key); err != nil {
		return err
	}
	if err := c.cassandraCache.Delete(ctx, key); err != nil {
		return err
	}
	return nil
}

func (c *twoTierCache) PurgeExpired(ctx context.Context) error {
	// Only Cassandra needs purging (Redis handles TTL automatically)
	return c.cassandraCache.PurgeExpired(ctx)
}

func (c *twoTierCache) Shutdown(ctx context.Context) {
	c.logger.Info("shutting down two-tier cache")
	c.redisCache.Shutdown(ctx)
	c.cassandraCache.Shutdown(ctx)
	c.logger.Info("two-tier cache shutdown complete")
}
