package cache

import (
	"context"
	"encoding/json"
	"fmt"
	"time"
)

// Cache is the main cache manager that wraps a Provider.
type Cache struct {
	provider Provider
}

// NewCache creates a new cache manager with the specified provider.
func NewCache(provider Provider) *Cache {
	return &Cache{
		provider: provider,
	}
}

// Get retrieves and deserializes a value from the cache.
func (c *Cache) Get(ctx context.Context, key string, dest interface{}) error {
	data, exists := c.provider.Get(ctx, key)
	if !exists {
		return fmt.Errorf("key not found: %s", key)
	}

	if err := json.Unmarshal(data, dest); err != nil {
		return fmt.Errorf("failed to deserialize: %w", err)
	}

	return nil
}

// GetBytes retrieves raw bytes from the cache.
func (c *Cache) GetBytes(ctx context.Context, key string) ([]byte, error) {
	data, exists := c.provider.Get(ctx, key)
	if !exists {
		return nil, fmt.Errorf("key not found: %s", key)
	}
	return data, nil
}

// Set serializes and stores a value in the cache with the specified TTL.
func (c *Cache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
	data, err := json.Marshal(value)
	if err != nil {
		return fmt.Errorf("failed to serialize: %w", err)
	}

	return c.provider.Set(ctx, key, data, ttl)
}

// SetBytes stores raw bytes in the cache with the specified TTL.
func (c *Cache) SetBytes(ctx context.Context, key string, value []byte, ttl time.Duration) error {
	return c.provider.Set(ctx, key, value, ttl)
}

// SetWithTags serializes and stores a value in the cache with the specified TTL and tags.
func (c *Cache) SetWithTags(ctx context.Context, key string, value interface{}, ttl time.Duration, tags []string) error {
	data, err := json.Marshal(value)
	if err != nil {
		return fmt.Errorf("failed to serialize: %w", err)
	}

	return c.provider.SetWithTags(ctx, key, data, ttl, tags)
}

// SetBytesWithTags stores raw bytes in the cache with the specified TTL and tags.
func (c *Cache) SetBytesWithTags(ctx context.Context, key string, value []byte, ttl time.Duration, tags []string) error {
	return c.provider.SetWithTags(ctx, key, value, ttl, tags)
}

// Delete removes a key from the cache.
func (c *Cache) Delete(ctx context.Context, key string) error {
	return c.provider.Delete(ctx, key)
}

// DeleteByTag removes all keys associated with the given tag.
func (c *Cache) DeleteByTag(ctx context.Context, tag string) error {
	return c.provider.DeleteByTag(ctx, tag)
}

// DeleteByPattern removes all keys matching the pattern.
func (c *Cache) DeleteByPattern(ctx context.Context, pattern string) error {
	return c.provider.DeleteByPattern(ctx, pattern)
}

// Clear removes all items from the cache.
func (c *Cache) Clear(ctx context.Context) error {
	return c.provider.Clear(ctx)
}

// Exists checks if a key exists in the cache.
func (c *Cache) Exists(ctx context.Context, key string) bool {
	return c.provider.Exists(ctx, key)
}

// Stats returns statistics about the cache.
func (c *Cache) Stats(ctx context.Context) (*CacheStats, error) {
	return c.provider.Stats(ctx)
}

// Close closes the cache and releases any resources.
func (c *Cache) Close() error {
	return c.provider.Close()
}

// GetOrSet retrieves a value from cache, or sets it if it doesn't exist.
// The loader function is called only if the key is not found in cache.
func (c *Cache) GetOrSet(ctx context.Context, key string, dest interface{}, ttl time.Duration, loader func() (interface{}, error)) error {
	// Try to get from cache first
	err := c.Get(ctx, key, dest)
	if err == nil {
		return nil
	}

	// Load the value
	value, err := loader()
	if err != nil {
		return fmt.Errorf("loader failed: %w", err)
	}

	// Store in cache
	if err := c.Set(ctx, key, value, ttl); err != nil {
		return fmt.Errorf("failed to cache value: %w", err)
	}

	// Populate dest with the loaded value
	data, err := json.Marshal(value)
	if err != nil {
		return fmt.Errorf("failed to serialize loaded value: %w", err)
	}

	if err := json.Unmarshal(data, dest); err != nil {
		return fmt.Errorf("failed to deserialize loaded value: %w", err)
	}

	return nil
}

// Remember is a convenience function that caches the result of a function call.
// It's similar to GetOrSet but returns the value directly.
func (c *Cache) Remember(ctx context.Context, key string, ttl time.Duration, loader func() (interface{}, error)) (interface{}, error) {
	// Try to get from cache first as bytes
	data, err := c.GetBytes(ctx, key)
	if err == nil {
		var result interface{}
		if err := json.Unmarshal(data, &result); err == nil {
			return result, nil
		}
	}

	// Load the value
	value, err := loader()
	if err != nil {
		return nil, fmt.Errorf("loader failed: %w", err)
	}

	// Store in cache
	if err := c.Set(ctx, key, value, ttl); err != nil {
		return nil, fmt.Errorf("failed to cache value: %w", err)
	}

	return value, nil
}
