package logs

import (
	"context"
	"fmt"
	"strings"
)

// contextKey 是用于在 context.Context 中存储值的私有类型，避免键冲突。
type contextKey string

const (
	// compNameKey 用于存储组件名称。
	compNameKey contextKey = "compName"
	// chainKey 用于存储调用链。
	chainKey contextKey = "chain"
)

// AddCompName 将一个组件名（对象名）存入 Context，并返回一个包含该信息的新 Context。
// 这通常在依赖注入时完成，用于创建组件的“身份名牌” selfCtx。
func AddCompName(ctx context.Context, compName string) context.Context {
	return context.WithValue(ctx, compNameKey, compName)
}

// AddFuncName 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息，
// 生成并返回一个包含更新后调用链和当前组件名的 新 Context。
// 此函数用于只需要传递调用链而不需要立即打印日志的场景。
func AddFuncName(upstreamCtx context.Context, selfCtx context.Context, funcName string) context.Context {
	// 1. 获取上游调用链
	var oldChain []string
	if val := upstreamCtx.Value(chainKey); val != nil {
		if chain, ok := val.([]string); ok {
			oldChain = chain
		}
	}

	// 2. 获取当前组件名
	compName, ok := selfCtx.Value(compNameKey).(string)
	if !ok {
		// 如果 selfCtx 中没有 compName，则无法构建节点，直接返回 upstreamCtx
		// 这种情况通常不应该发生，因为 selfCtx 应该在依赖注入时通过 AddCompName 初始化
		return upstreamCtx
	}

	// 3. 构建新节点
	newNode := fmt.Sprintf("%s.%s", compName, funcName)

	// 4. 生成新调用链
	// 创建一个新的切片，并将旧链和新节点复制进去
	newChain := make([]string, len(oldChain)+1)
	copy(newChain, oldChain)
	newChain[len(oldChain)] = newNode

	// 5. 创建新的 Context
	// 使用 context.WithValue，以 chainKey 为键，将 newChain 存入一个新的 Context，我们称之为 tmpCtx。
	tmpCtx := context.WithValue(upstreamCtx, chainKey, newChain)

	// 接着，基于 tmpCtx，再次调用 context.WithValue，以 compNameKey 为键，
	// 将从 selfCtx 中获取的 compName 存入，得到最终的 newCtx。
	// 这确保了传向下游的 Context 正确地标识了当前组件。
	newCtx := context.WithValue(tmpCtx, compNameKey, compName)

	return newCtx
}

// DetachContext 创建一个“分离”的 Context。
// 新的 Context 会继承原始 Context 中的所有值（特别是用于日志追踪的 chainKey 和 compNameKey），
// 但它会使用 context.Background() 作为其父级，从而“丢弃”原始 Context 的取消信号。
// 这对于需要在请求结束后继续执行的异步任务（如记录审计日志）至关重要，可以防止出现 "context canceled" 错误。
func DetachContext(ctx context.Context) context.Context {
	detachedCtx := context.Background()

	// 复制我们关心的、用于日志追踪的所有值
	if val := ctx.Value(chainKey); val != nil {
		detachedCtx = context.WithValue(detachedCtx, chainKey, val)
	}
	if val := ctx.Value(compNameKey); val != nil {
		detachedCtx = context.WithValue(detachedCtx, compNameKey, val)
	}

	return detachedCtx
}

// 获取context中的调用链字符串
func GetTraceStr(ctx context.Context) string {
	if trace, ok := ctx.Value(chainKey).([]string); ok {
		return strings.Join(trace, "->")
	}
	return ""
}
