// Package pregel provides asynchronous caching support for Pregel execution.
package pregel

import (
	"context"
	"time"
)

// AsyncCache extends the Cache interface with asynchronous operations.
type AsyncCache interface {
	Cache

	// AGet asynchronously retrieves a value from the cache.
	// Returns a channel that will receive the result.
	AGet(ctx context.Context, key string) <-chan CacheResult

	// ASet asynchronously stores a value in the cache.
	// Returns a channel that will be closed when the operation completes.
	ASet(ctx context.Context, key string, value any, ttl time.Duration) <-chan error

	// ADelete asynchronously removes a value from the cache.
	// Returns a channel that will be closed when the operation completes.
	ADelete(ctx context.Context, key string) <-chan error
}

// CacheResult represents the result of an asynchronous cache get operation.
type CacheResult struct {
	Value any
	Found bool
	Error error
}

// AsyncMemoryCache is an asynchronous in-memory cache implementation.
type AsyncMemoryCache struct {
	*MemoryCache
	workerCh chan asyncCacheOp
	stopCh   chan struct{}
}

// asyncCacheOp represents an asynchronous cache operation.
type asyncCacheOp struct {
	ctx    context.Context
	opType string // "get", "set", "delete"
	key    string
	value  any
	ttl    time.Duration
	result chan<- CacheResult
	done   chan<- error
}

// NewAsyncMemoryCache creates a new asynchronous in-memory cache.
func NewAsyncMemoryCache(maxSize int, eviction EvictionPolicy, numWorkers int) *AsyncMemoryCache {
	if numWorkers <= 0 {
		numWorkers = 4
	}

	cache := &AsyncMemoryCache{
		MemoryCache: NewMemoryCache(maxSize, eviction),
		workerCh:    make(chan asyncCacheOp, 1000),
		stopCh:      make(chan struct{}),
	}

	// Start worker goroutines
	for i := 0; i < numWorkers; i++ {
		go cache.worker()
	}

	return cache
}

// worker processes asynchronous cache operations.
func (c *AsyncMemoryCache) worker() {
	for {
		select {
		case op := <-c.workerCh:
			c.processOp(op)
		case <-c.stopCh:
			return
		}
	}
}

// processOp processes a single cache operation.
func (c *AsyncMemoryCache) processOp(op asyncCacheOp) {
	switch op.opType {
	case "get":
		value, found := c.MemoryCache.Get(op.ctx, op.key)
		if op.result != nil {
			op.result <- CacheResult{Value: value, Found: found}
		}
	case "set":
		c.MemoryCache.Set(op.ctx, op.key, op.value, op.ttl)
		if op.done != nil {
			op.done <- nil
		}
	case "delete":
		c.MemoryCache.Delete(op.ctx, op.key)
		if op.done != nil {
			op.done <- nil
		}
	}
}

// AGet asynchronously retrieves a value from the cache.
func (c *AsyncMemoryCache) AGet(ctx context.Context, key string) <-chan CacheResult {
	resultCh := make(chan CacheResult, 1)

	select {
	case c.workerCh <- asyncCacheOp{
		ctx:    ctx,
		opType: "get",
		key:    key,
		result: resultCh,
	}:
	case <-ctx.Done():
		resultCh <- CacheResult{Error: ctx.Err()}
		close(resultCh)
	}

	return resultCh
}

// ASet asynchronously stores a value in the cache.
func (c *AsyncMemoryCache) ASet(ctx context.Context, key string, value any, ttl time.Duration) <-chan error {
	doneCh := make(chan error, 1)

	select {
	case c.workerCh <- asyncCacheOp{
		ctx:    ctx,
		opType: "set",
		key:    key,
		value:  value,
		ttl:    ttl,
		done:   doneCh,
	}:
	case <-ctx.Done():
		doneCh <- ctx.Err()
		close(doneCh)
	}

	return doneCh
}

// ADelete asynchronously removes a value from the cache.
func (c *AsyncMemoryCache) ADelete(ctx context.Context, key string) <-chan error {
	doneCh := make(chan error, 1)

	select {
	case c.workerCh <- asyncCacheOp{
		ctx:    ctx,
		opType: "delete",
		key:    key,
		done:   doneCh,
	}:
	case <-ctx.Done():
		doneCh <- ctx.Err()
		close(doneCh)
	}

	return doneCh
}

// Stop stops the async cache workers.
func (c *AsyncMemoryCache) Stop() {
	close(c.stopCh)
}

// AsyncCachePolicy configures async cache behavior.
type AsyncCachePolicy struct {
	// KeyFunc generates the cache key.
	KeyFunc func(context.Context, any) string

	// TTL is the time-to-live for cached values.
	TTL *time.Duration

	// Async determines if operations should be async.
	Async bool
}

// AsyncCachedExecutor wraps a function with async caching.
type AsyncCachedExecutor struct {
	cache       AsyncCache
	cachePolicy *AsyncCachePolicy
}

// NewAsyncCachedExecutor creates a new async cached executor.
func NewAsyncCachedExecutor(cache AsyncCache, policy *AsyncCachePolicy) *AsyncCachedExecutor {
	return &AsyncCachedExecutor{
		cache:       cache,
		cachePolicy: policy,
	}
}

// Execute executes a function with async caching.
func (e *AsyncCachedExecutor) Execute(
	ctx context.Context,
	nodeName string,
	input any,
	fn func(context.Context, any) (any, error),
) (any, error) {
	// Generate cache key
	var key string
	if e.cachePolicy != nil && e.cachePolicy.KeyFunc != nil {
		key = e.cachePolicy.KeyFunc(ctx, input)
	} else {
		key = GenerateCacheKey(nodeName, input)
	}

	// Check cache asynchronously
	if e.cachePolicy != nil && e.cachePolicy.Async {
		resultCh := e.cache.AGet(ctx, key)

		select {
		case result := <-resultCh:
			if result.Error != nil {
				return nil, result.Error
			}
			if result.Found {
				return result.Value, nil
			}
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	} else {
		// Synchronous fallback
		if cached, ok := e.cache.Get(ctx, key); ok {
			return cached, nil
		}
	}

	// Execute function
	result, err := fn(ctx, input)
	if err != nil {
		return nil, err
	}

	// Cache result asynchronously
	var ttl time.Duration
	if e.cachePolicy != nil && e.cachePolicy.TTL != nil {
		ttl = *e.cachePolicy.TTL
	}

	if e.cachePolicy != nil && e.cachePolicy.Async {
		// Fire and forget async set
		e.cache.ASet(context.Background(), key, result, ttl)
	} else {
		e.cache.Set(ctx, key, result, ttl)
	}

	return result, nil
}

// WaitForPending waits for all pending async operations to complete.
func (e *AsyncCachedExecutor) WaitForPending(timeout time.Duration) bool {
	// In a real implementation, this would track pending operations
	// For now, just sleep briefly to allow operations to complete
	time.Sleep(timeout)
	return true
}
