package cache

import (
	"context"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/gogf/gf/v2/frame/g"

	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/glog"

	"github.com/duke-git/lancet/v2/slice"
	"github.com/gogf/gf/v2/net/gtrace"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/util/gconv"
	"github.com/gogf/gf/v2/util/guid"
	"go.opentelemetry.io/otel/trace"
)

var (
	cache *Cache
)

type Cache struct {
	*gcache.Cache
}

type TraceContext struct {
	TraceID    string
	SpanID     string
	ParentID   string
	Attributes map[string]string
}

type PrefixEnum string

const (
	PrefixRedeemType                           PrefixEnum = "redeem_type"
	PrefixRedeemWithPaymentType                PrefixEnum = "redeem_with_payment_type"
	PrefixRedeemAppleAccountLimitedType        PrefixEnum = "redeem_apple_account_limited_type"
	PrefixAccountLimiterType                   PrefixEnum = "account_limiter_type"
	PrefixJDAccountQueryCache                  PrefixEnum = "jd_account_query_cache"
	PrefixJDAccountQueryBalanceWithCookie      PrefixEnum = "jd_account_query_cache_with_cookie"
	PrefixWalmartAccountQueryCache             PrefixEnum = "walmart_account_query_cache"
	PrefixWalmartAccountQueryBalanceWithCookie PrefixEnum = "walmart_account_query_cache_with_cookie"
	PrefixAppleMachineAccount                  PrefixEnum = "MachineCurrentAccountId"
	PrefixAppleAccount                         PrefixEnum = "apple_account"
	PrefixAppleOrder                           PrefixEnum = "apple_order"
	PrefixAppleDuplicatedOrder                 PrefixEnum = "apple_duplicated_order"
	PrefixJdPaymentCheck                       PrefixEnum = "jd_payment_check" // 支付状态检查缓存
	PrefixJdCardExtract                        PrefixEnum = "jd_card_extract"  // 卡密提取锁定缓存
	PrefixTrace                                PrefixEnum = "trace"
	PrefixTracePigAccount                      PrefixEnum = "trace_pig_account"
	PrefixProxyPool                            PrefixEnum = "proxy_pool"
)

func (e PrefixEnum) Key(key interface{}) string {
	return gconv.String(e) + ":" + gconv.String(key)
}

func (e PrefixEnum) String() string {
	return string(e)
}

func NewCache() *Cache {
	if cache == nil {
		sync.OnceFunc(func() {
			cache = &Cache{
				Cache: gcache.New(),
			}
			cache.SetAdapter(gcache.NewAdapterRedis(g.Redis()))
		})()
	}
	return cache
}

// Incr 设置计数器缓存
func (i *Cache) Incr(ctx context.Context, key string, duration time.Duration) (err error) {
	result, err := i.Get(ctx, key)
	if result == nil || result.IsNil() {
		_ = i.Set(ctx, key, 1, duration)
		return
	}
	_, _, err = i.Update(ctx, key, result.Int()+1)
	return
}

// Decr 递减缓存
func (i *Cache) Decr(ctx context.Context, key string, duration time.Duration) (err error) {
	result, err := i.Get(ctx, key)
	if result == nil || result.IsNil() {
		_ = i.Set(ctx, key, 0, duration)
		return
	}
	_, _, err = i.Update(ctx, key, result.Int()-1)
	return
}

// GetPrefixKeyNum 获取指定以键为开头的键值对个数
func (i *Cache) GetPrefixKeyNum(ctx context.Context, key interface{}) (count int) {
	keys, _ := i.Keys(ctx)
	count = slice.CountBy(keys, func(index int, item interface{}) bool {
		return strings.HasPrefix(item.(string), key.(string))
	})
	return
}

// GetPrefixKey 获取指定以键为开头的键值对个数
func (i *Cache) GetPrefixKey(ctx context.Context, key interface{}) (keys []interface{}) {
	keys, _ = i.Keys(ctx)
	keys = slice.Filter(keys, func(index int, item interface{}) bool {
		return strings.HasPrefix(item.(string), key.(string))
	})
	return keys
}

// SaveTrace 存储追踪信息到Redis中
// SaveTraceToRedis 保存追踪信息到Redis
func (i *Cache) SaveTrace(ctx context.Context, key string) error {
	spanCtx := trace.SpanContextFromContext(ctx)
	if spanCtx.IsValid() {
		return i.Set(ctx, PrefixTrace.Key(key), spanCtx, time.Minute*10)
	}
	return nil
}

// GetTrace 获取追踪信息（恢复追踪能力）
func (i *Cache) GetTrace(ctx context.Context, key string) context.Context {
	value, err := i.Get(ctx, PrefixTrace.Key(key))
	if err != nil || value == nil || value.IsNil() || reflect.TypeOf(value.Interface()) != reflect.TypeOf(trace.SpanContext{}) {
		return ctx
	}
	spanCtx, ok := value.Interface().(trace.SpanContext)
	if !ok {
		glog.Error(ctx, "获取追踪信息失败，类型错误")
		return ctx
	}
	return trace.ContextWithRemoteSpanContext(ctx, spanCtx)
}

// GetTraceID 获取追踪的唯一id
func (i *Cache) GetTraceID(ctx context.Context) string {
	return gtrace.GetSpanID(ctx) + "-" + guid.S()
}

// DeleteTrace 删除追踪信息
func (i *Cache) DeleteTrace(ctx context.Context, key string) {
	_, _ = i.Remove(ctx, PrefixTrace.Key(key))
}

// TryLock 尝试获取分布式锁
// key: 锁的键名
// ttl: 锁的过期时间
// 返回值: 是否获取成功，锁的唯一标识，错误信息
func (i *Cache) TryLock(ctx context.Context, key string, ttl time.Duration) (bool, string, error) {
	lockValue := guid.S() // 生成唯一标识

	// 使用自定义cache模块的SetIfNotExist方法实现分布式锁
	// SetIfNotExist返回true表示设置成功（key不存在），false表示key已存在
	success, err := i.SetIfNotExist(ctx, key, lockValue, ttl)
	if err != nil {
		return false, "", err // 其他错误
	}

	if !success {
		return false, "", nil // key已存在，获取锁失败
	}

	return true, lockValue, nil
}

// Lock 获取分布式锁（阻塞式）
// key: 锁的键名
// ttl: 锁的过期时间
// timeout: 获取锁的超时时间
// 返回值: 锁的唯一标识，错误信息
func (i *Cache) Lock(ctx context.Context, key string, ttl time.Duration, timeout time.Duration) (string, error) {
	startTime := time.Now()

	for {
		success, lockValue, err := i.TryLock(ctx, key, ttl)
		if err != nil {
			return "", err
		}

		if success {
			return lockValue, nil
		}

		// 检查是否超时
		if time.Since(startTime) > timeout {
			return "", gerror.New("获取分布式锁超时")
		}

		// 短暂等待后重试
		time.Sleep(time.Millisecond * 10)
	}
}

// Unlock 释放分布式锁
// key: 锁的键名
// lockValue: 锁的唯一标识（用于确保只有锁的持有者才能释放锁）
func (i *Cache) Unlock(ctx context.Context, key string, lockValue string) error {
	// 获取当前锁的值
	currentValue, err := i.Get(ctx, key)
	if err != nil {
		return err
	}

	if currentValue == nil || currentValue.IsNil() {
		// 锁不存在，无需释放
		return nil
	}

	// 检查锁的值是否匹配，确保只有锁的持有者才能释放锁
	if currentValue.String() != lockValue {
		return gerror.New("无权释放分布式锁，锁的持有者不匹配")
	}

	// 删除锁
	_, err = i.Remove(ctx, key)
	return err
}
