package memory

import (
	"context"
	"encoding/json"
	"sync"
	"time"

	"github.com/rwadurian/mpc-system/services/account/application/ports"
)

// cacheEntry holds a cached value with its expiration time
type cacheEntry struct {
	value     []byte
	expiresAt time.Time
}

// CacheAdapter implements CacheService using in-memory storage
type CacheAdapter struct {
	mu    sync.RWMutex
	store map[string]cacheEntry
}

// NewCacheAdapter creates a new in-memory CacheAdapter
func NewCacheAdapter() ports.CacheService {
	adapter := &CacheAdapter{
		store: make(map[string]cacheEntry),
	}
	// Start cleanup goroutine
	go adapter.cleanupLoop()
	return adapter
}

// cleanupLoop periodically removes expired entries
func (c *CacheAdapter) cleanupLoop() {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop()

	for range ticker.C {
		c.cleanup()
	}
}

// cleanup removes expired entries
func (c *CacheAdapter) cleanup() {
	c.mu.Lock()
	defer c.mu.Unlock()

	now := time.Now()
	for key, entry := range c.store {
		if !entry.expiresAt.IsZero() && now.After(entry.expiresAt) {
			delete(c.store, key)
		}
	}
}

// Set sets a value in the cache
func (c *CacheAdapter) Set(ctx context.Context, key string, value interface{}, ttlSeconds int) error {
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	entry := cacheEntry{
		value: data,
	}
	if ttlSeconds > 0 {
		entry.expiresAt = time.Now().Add(time.Duration(ttlSeconds) * time.Second)
	}

	c.store[key] = entry
	return nil
}

// Get gets a value from the cache
func (c *CacheAdapter) Get(ctx context.Context, key string) (interface{}, error) {
	c.mu.RLock()
	entry, exists := c.store[key]
	c.mu.RUnlock()

	if !exists {
		return nil, nil
	}

	// Check expiration
	if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
		c.mu.Lock()
		delete(c.store, key)
		c.mu.Unlock()
		return nil, nil
	}

	var value interface{}
	if err := json.Unmarshal(entry.value, &value); err != nil {
		return nil, err
	}

	return value, nil
}

// Delete deletes a value from the cache
func (c *CacheAdapter) Delete(ctx context.Context, key string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	delete(c.store, key)
	return nil
}

// Exists checks if a key exists in the cache
func (c *CacheAdapter) Exists(ctx context.Context, key string) (bool, error) {
	c.mu.RLock()
	entry, exists := c.store[key]
	c.mu.RUnlock()

	if !exists {
		return false, nil
	}

	// Check expiration
	if !entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt) {
		c.mu.Lock()
		delete(c.store, key)
		c.mu.Unlock()
		return false, nil
	}

	return true, nil
}

// Ensure interface compliance
var _ ports.CacheService = (*CacheAdapter)(nil)
