package runner

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"strings"
)

type cacheAffinityKeys struct {
	Primary string
	Lookup  []string
	Record  []string
}

func buildCacheAffinityKey(kind string, modelType string, body map[string]any) string {
	return buildCacheAffinityKeys(kind, modelType, body).Primary
}

func buildCacheAffinityKeys(kind string, modelType string, body map[string]any) cacheAffinityKeys {
	if len(body) == 0 {
		return cacheAffinityKeys{}
	}
	for _, key := range []string{"cacheAffinityKey", "cache_affinity_key"} {
		if value := strings.TrimSpace(stringFromAny(body[key])); value != "" {
			key := "explicit:" + sha256Text(value)
			return cacheAffinityKeys{Primary: key, Lookup: []string{key}, Record: []string{key}}
		}
	}
	for _, key := range []string{"sessionId", "session_id", "conversationId", "conversation_id"} {
		if value := strings.TrimSpace(stringFromAny(body[key])); value != "" {
			key := key + ":" + sha256Text(value)
			return cacheAffinityKeys{Primary: key, Lookup: []string{key}, Record: []string{key}}
		}
	}
	if !cacheAffinityPromptHashSupported(kind, modelType) {
		return cacheAffinityKeys{}
	}
	basePayload := map[string]any{
		"kind":      kind,
		"modelType": modelType,
	}
	if tools, ok := body["tools"]; ok {
		basePayload["tools"] = tools
	}
	if instructions, ok := body["instructions"]; ok {
		basePayload["instructions"] = instructions
	}
	messageHashes := cacheAffinityMessageHashes(body)
	if len(messageHashes) > 0 {
		baseHash := stableJSONHash(basePayload)
		keys := make([]string, 0, len(messageHashes))
		for index := range messageHashes {
			keyPayload := map[string]any{
				"base":          baseHash,
				"messageHashes": messageHashes[:index+1],
				"prefixLength":  index + 1,
			}
			keys = append(keys, "prompt_lcp:"+stableJSONHash(keyPayload))
		}
		if len(keys) == 0 {
			return cacheAffinityKeys{}
		}
		lookup := make([]string, 0, len(keys))
		for index := len(keys) - 1; index >= 0; index-- {
			lookup = append(lookup, keys[index])
		}
		return cacheAffinityKeys{
			Primary: keys[len(keys)-1],
			Lookup:  lookup,
			Record:  keys,
		}
	}
	if input, ok := body["input"]; ok {
		basePayload["input"] = input
	}
	if len(basePayload) <= 2 {
		return cacheAffinityKeys{}
	}
	key := "prompt:" + stableJSONHash(basePayload)
	return cacheAffinityKeys{Primary: key, Lookup: []string{key}, Record: []string{key}}
}

func cacheAffinityPromptHashSupported(kind string, modelType string) bool {
	switch strings.TrimSpace(kind) {
	case "chat.completions", "responses":
		return true
	}
	switch strings.TrimSpace(modelType) {
	case "chat", "text_generate", "responses":
		return true
	default:
		return false
	}
}

func cacheAffinityMessageHashes(body map[string]any) []string {
	if hashes := stringListFromAny(body["messageHashes"]); len(hashes) > 0 {
		return hashes
	}
	if hashes := stringListFromAny(body["message_hashes"]); len(hashes) > 0 {
		return hashes
	}
	messages, ok := body["messages"].([]any)
	if !ok || len(messages) == 0 {
		return nil
	}
	hashes := make([]string, 0, len(messages))
	for _, message := range messages {
		raw, err := json.Marshal(message)
		if err != nil || len(raw) == 0 {
			continue
		}
		hashes = append(hashes, sha256Text(string(raw)))
	}
	return hashes
}

func stableJSONHash(value any) string {
	raw, err := json.Marshal(value)
	if err != nil || len(raw) == 0 {
		return ""
	}
	return sha256Text(string(raw))
}

func sha256Text(value string) string {
	sum := sha256.Sum256([]byte(value))
	return hex.EncodeToString(sum[:])
}
