package database

import (
	"context"
	"encoding/json"
	"log"
	"net"
	"sync"
	"time"
	"xboard-go/internal/config"

	"github.com/redis/go-redis/v9"
)

var Redis *redis.Client

type memoryEntry struct {
	Value     []byte
	ExpiresAt time.Time
}

type memoryCache struct {
	mu    sync.RWMutex
	items map[string]memoryEntry
}

var fallbackCache = &memoryCache{
	items: make(map[string]memoryEntry),
}

func InitCache() {
	if config.AppConfig.RedisHost == "" {
		if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
			log.Printf("Redis host not configured, using in-memory cache")
		}
		return
	}

	addr := net.JoinHostPort(config.AppConfig.RedisHost, config.AppConfig.RedisPort)
	client := redis.NewClient(&redis.Options{
		Addr:     addr,
		Password: config.AppConfig.RedisPass,
		DB:       config.AppConfig.RedisDB,
	})

	// Fast timeout for initial connectivity check to avoid blocking startup
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	if err := client.Ping(ctx).Err(); err != nil {
		if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
			log.Printf("Redis unavailable at %s, falling back to in-memory: %v", addr, err)
		}
		return
	}

	Redis = client
	if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
		log.Printf("Redis connection established at %s", addr)
	}
}

func CacheSet(key string, value any, ttl time.Duration) error {
	payload, err := json.Marshal(value)
	if err != nil {
		return err
	}

	if Redis != nil {
		if err := Redis.Set(context.Background(), key, payload, ttl).Err(); err == nil {
			return nil
		}
	}

	fallbackCache.Set(key, payload, ttl)
	return nil
}

func CacheDelete(key string) error {
	if Redis != nil {
		if err := Redis.Del(context.Background(), key).Err(); err == nil {
			fallbackCache.Delete(key)
			return nil
		}
	}

	fallbackCache.Delete(key)
	return nil
}

func CacheGetJSON[T any](key string) (T, bool) {
	var zero T
	var payload []byte

	if Redis != nil {
		value, err := Redis.Get(context.Background(), key).Bytes()
		if err == nil {
			payload = value
		}
	}

	if len(payload) == 0 {
		value, ok := fallbackCache.Get(key)
		if !ok {
			return zero, false
		}
		payload = value
	}

	var result T
	if err := json.Unmarshal(payload, &result); err != nil {
		return zero, false
	}

	return result, true
}

func (m *memoryCache) Set(key string, value []byte, ttl time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()

	entry := memoryEntry{
		Value: value,
	}
	if ttl > 0 {
		entry.ExpiresAt = time.Now().Add(ttl)
	}
	m.items[key] = entry
}

func (m *memoryCache) Get(key string) ([]byte, bool) {
	m.mu.RLock()
	entry, ok := m.items[key]
	m.mu.RUnlock()
	if !ok {
		return nil, false
	}

	if !entry.ExpiresAt.IsZero() && time.Now().After(entry.ExpiresAt) {
		m.Delete(key)
		return nil, false
	}

	return entry.Value, true
}

func (m *memoryCache) Delete(key string) {
	m.mu.Lock()
	defer m.mu.Unlock()
	delete(m.items, key)
}
