// Package idempotency 提供创建类命令的幂等身份与请求指纹公共契约。
package idempotency

import (
	"crypto/sha256"
	"encoding/hex"
	"reflect"
	"strings"

	"github.com/bytedance/sonic"
)

const (
	// FingerprintAlgorithmV1 是请求指纹算法的首个稳定版本。
	FingerprintAlgorithmV1 = "sha256-canonical-json-v1"
)

// Scope 表示请求 ID 的幂等作用域。
type Scope struct {
	Subject   string `json:"subject"`
	Operation string `json:"operation"`
}

// FingerprintValue 表示带算法版本的请求指纹。
type FingerprintValue struct {
	Algorithm string `json:"algorithm"`
	Value     string `json:"value"`
}

// Record 表示业务模块自行持久化的幂等事实最小投影。
type Record struct {
	Scope       Scope            `json:"scope"`
	RequestID   string           `json:"request_id"`
	Fingerprint FingerprintValue `json:"fingerprint"`
}

// ReplayKind 表示已有事实与本次命令的关系。
type ReplayKind string

const (
	// ReplaySame 表示相同命令重放，应返回原业务结果。
	ReplaySame ReplayKind = "same"
	// ReplayConflict 表示同作用域、同请求 ID 携带了不同业务内容。
	ReplayConflict ReplayKind = "conflict"
	// ReplayUnrelated 表示不是同一幂等作用域内的命令。
	ReplayUnrelated ReplayKind = "unrelated"
)

var volatileFieldNames = map[string]struct{}{
	"authorization": {},
	"cookie":        {},
	"nonce":         {},
	"sign":          {},
	"signature":     {},
	"timestamp":     {},
	"token":         {},
}

// Fingerprint 对影响业务结果的规范化字段计算稳定指纹。
//
// 调用方仍应优先传入专用命令 DTO；本函数会递归排除约定的易变传输字段，
// 并去除字符串首尾空白，避免签名、令牌或对象字段顺序污染业务身份。
func Fingerprint(command any) (FingerprintValue, error) {
	raw, err := sonic.ConfigStd.Marshal(command)
	if err != nil {
		return FingerprintValue{}, err
	}
	var value any
	if err := sonic.Unmarshal(raw, &value); err != nil {
		return FingerprintValue{}, err
	}
	normalized := normalize(value)
	canonical, err := sonic.ConfigStd.Marshal(normalized)
	if err != nil {
		return FingerprintValue{}, err
	}
	sum := sha256.Sum256(canonical)
	return FingerprintValue{
		Algorithm: FingerprintAlgorithmV1,
		Value:     hex.EncodeToString(sum[:]),
	}, nil
}

// ValidateScope 校验幂等作用域和稳定请求 ID 是否完整。
func ValidateScope(scope Scope, requestID string) bool {
	return strings.TrimSpace(scope.Subject) != "" &&
		strings.TrimSpace(scope.Operation) != "" &&
		strings.TrimSpace(requestID) != ""
}

// Classify 比较已有幂等事实与本次命令。
func Classify(existing Record, scope Scope, requestID string, fingerprint FingerprintValue) ReplayKind {
	if existing.Scope != scope || existing.RequestID != requestID {
		return ReplayUnrelated
	}
	if reflect.DeepEqual(existing.Fingerprint, fingerprint) {
		return ReplaySame
	}
	return ReplayConflict
}

func normalize(value any) any {
	switch typed := value.(type) {
	case map[string]any:
		result := make(map[string]any, len(typed))
		for key, item := range typed {
			if _, volatile := volatileFieldNames[strings.ToLower(strings.TrimSpace(key))]; volatile {
				continue
			}
			result[key] = normalize(item)
		}
		return result
	case []any:
		result := make([]any, len(typed))
		for index, item := range typed {
			result[index] = normalize(item)
		}
		return result
	case string:
		return strings.TrimSpace(typed)
	default:
		return value
	}
}
