// monorepo/cloud/maplefile-backend/pkg/storage/cache/twotiercache/twotiercache.go
package twotiercache

import (
	"context"
	"time"

	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/storage/cache/cassandracache"
	"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/storage/memory/redis"
	"go.uber.org/zap"
)

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
}

// twoTierCacheImpl: 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 twoTierCacheImpl struct {
	RedisCache     redis.Cacher
	CassandraCache cassandracache.CassandraCacher
	Logger         *zap.Logger
}

func NewTwoTierCache(redisCache redis.Cacher, cassandraCache cassandracache.CassandraCacher, logger *zap.Logger) TwoTierCacher {
	logger = logger.Named("TwoTierCache")
	return &twoTierCacheImpl{
		RedisCache:     redisCache,
		CassandraCache: cassandraCache,
		Logger:         logger,
	}
}

func (c *twoTierCacheImpl) Get(ctx context.Context, key string) ([]byte, error) {
	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
	}

	val, err = c.CassandraCache.Get(ctx, key)
	if err != nil {
		return nil, err
	}
	if val != nil {
		c.Logger.Debug("cache hit from Cassandra, writing back to Redis", zap.String("key", key))
		_ = c.RedisCache.Set(ctx, key, val)
	}
	return val, nil
}

func (c *twoTierCacheImpl) Set(ctx context.Context, key string, val []byte) error {
	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 *twoTierCacheImpl) SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error {
	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 *twoTierCacheImpl) Delete(ctx context.Context, key string) error {
	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 *twoTierCacheImpl) PurgeExpired(ctx context.Context) error {
	return c.CassandraCache.PurgeExpired(ctx)
}

func (c *twoTierCacheImpl) Shutdown(ctx context.Context) {
	c.Logger.Info("two-tier cache shutting down...")
	c.RedisCache.Shutdown(ctx)
	c.CassandraCache.Shutdown()
	c.Logger.Info("two-tier cache shutdown complete")
}
