package ai

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

	"github.com/zeromicro/go-zero/core/stores/redis"
)

const defaultRedisNamespace = "haixun:dev:v1"

// RedisModelsCache shares model-list entries and refresh locks across processes.
type RedisModelsCache struct {
	client    *redis.Redis
	namespace string
}

func NewRedisModelsCache(conf redis.RedisConf, namespace string) *RedisModelsCache {
	return &RedisModelsCache{client: redis.MustNewRedis(conf), namespace: normalizeRedisNamespace(namespace)}
}

func (c *RedisModelsCache) Get(ctx context.Context, key ModelsCacheKey) (ModelsCacheValue, bool, error) {
	raw, err := c.client.GetCtx(ctx, c.valueKey(key))
	if err != nil {
		return ModelsCacheValue{}, false, err
	}
	if raw == "" {
		return ModelsCacheValue{}, false, nil
	}
	var value ModelsCacheValue
	if err := json.Unmarshal([]byte(raw), &value); err != nil {
		return ModelsCacheValue{}, false, err
	}
	return value, true, nil
}

func (c *RedisModelsCache) Set(ctx context.Context, key ModelsCacheKey, value ModelsCacheValue, ttl time.Duration) error {
	raw, err := json.Marshal(value)
	if err != nil {
		return err
	}
	return c.client.SetexCtx(ctx, c.valueKey(key), string(raw), durationSeconds(ttl))
}

func (c *RedisModelsCache) AcquireRefresh(ctx context.Context, key ModelsCacheKey, token string, ttl time.Duration) (bool, error) {
	return c.client.SetnxExCtx(ctx, c.lockKey(key), token, durationSeconds(ttl))
}

func (c *RedisModelsCache) ReleaseRefresh(ctx context.Context, key ModelsCacheKey, token string) error {
	_, err := c.client.EvalCtx(ctx, `
if redis.call('GET', KEYS[1]) == ARGV[1] then
  return redis.call('DEL', KEYS[1])
end
return 0
`, []string{c.lockKey(key)}, token)
	return err
}

func (c *RedisModelsCache) valueKey(key ModelsCacheKey) string {
	return fmt.Sprintf("%s:ai:models:{%s:%s}:value", c.namespace, key.Provider, key.CredentialFingerprint)
}

func (c *RedisModelsCache) lockKey(key ModelsCacheKey) string {
	return fmt.Sprintf("%s:ai:models:{%s:%s}:lock", c.namespace, key.Provider, key.CredentialFingerprint)
}

func normalizeRedisNamespace(namespace string) string {
	if namespace = strings.Trim(strings.TrimSpace(namespace), ":"); namespace != "" {
		return namespace
	}
	return defaultRedisNamespace
}

func durationSeconds(ttl time.Duration) int {
	seconds := int(ttl.Seconds())
	if seconds < 1 {
		return 1
	}
	return seconds
}
