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

import (
	"context"
	"time"

	"github.com/gocql/gocql"
	"go.uber.org/zap"
)

type CassandraCacher interface {
	Shutdown()
	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
}

type cache struct {
	Session *gocql.Session
	Logger  *zap.Logger
}

func NewCassandraCacher(session *gocql.Session, logger *zap.Logger) CassandraCacher {
	logger = logger.Named("CassandraCache")
	logger.Info("cassandra cache initialized")
	return &cache{
		Session: session,
		Logger:  logger,
	}
}

func (s *cache) Shutdown() {
	s.Logger.Info("cassandra cache shutting down...")
	s.Session.Close()
}

func (s *cache) Get(ctx context.Context, key string) ([]byte, error) {
	var value []byte
	var expiresAt time.Time

	query := `SELECT value, expires_at FROM pkg_cache_by_key_with_asc_expire_at WHERE key=?`
	err := s.Session.Query(query, key).WithContext(ctx).Consistency(gocql.LocalQuorum).Scan(&value, &expiresAt)

	if err == gocql.ErrNotFound {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	// Check if expired in application code
	if time.Now().After(expiresAt) {
		// Entry is expired, delete it and return nil
		_ = s.Delete(ctx, key) // Clean up expired entry
		return nil, nil
	}

	return value, nil
}

func (s *cache) Set(ctx context.Context, key string, val []byte) error {
	expiresAt := time.Now().Add(24 * time.Hour) // Default 24 hour expiry
	return s.Session.Query(`INSERT INTO pkg_cache_by_key_with_asc_expire_at (key, expires_at, value) VALUES (?, ?, ?)`,
		key, expiresAt, val).WithContext(ctx).Consistency(gocql.LocalQuorum).Exec()
}

func (s *cache) SetWithExpiry(ctx context.Context, key string, val []byte, expiry time.Duration) error {
	expiresAt := time.Now().Add(expiry)
	return s.Session.Query(`INSERT INTO pkg_cache_by_key_with_asc_expire_at (key, expires_at, value) VALUES (?, ?, ?)`,
		key, expiresAt, val).WithContext(ctx).Consistency(gocql.LocalQuorum).Exec()
}

func (s *cache) Delete(ctx context.Context, key string) error {
	return s.Session.Query(`DELETE FROM pkg_cache_by_key_with_asc_expire_at WHERE key=?`,
		key).WithContext(ctx).Consistency(gocql.LocalQuorum).Exec()
}

func (s *cache) PurgeExpired(ctx context.Context) error {
	now := time.Now()

	// Thanks to the index on expires_at, this query is efficient
	iter := s.Session.Query(`SELECT key FROM pkg_cache_by_key_with_asc_expire_at WHERE expires_at < ? ALLOW FILTERING`,
		now).WithContext(ctx).Iter()

	var expiredKeys []string
	var key string
	for iter.Scan(&key) {
		expiredKeys = append(expiredKeys, key)
	}

	if err := iter.Close(); err != nil {
		return err
	}

	// Delete expired keys in batch
	if len(expiredKeys) > 0 {
		batch := s.Session.NewBatch(gocql.LoggedBatch).WithContext(ctx)
		for _, expiredKey := range expiredKeys {
			batch.Query(`DELETE FROM pkg_cache_by_key_with_asc_expire_at WHERE key=?`, expiredKey)
		}
		return s.Session.ExecuteBatch(batch)
	}

	return nil
}
