/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package protocol

// Copy from https://github.com/aws/aws-sdk-go
// May have been modified by Beijing Volcanoengine Technology Ltd.

import (
	"crypto/rand"
	"fmt"
	"reflect"
)

// RandReader is the random reader the protocol package will use to read
// random bytes from. This is exported for testing, and should not be used.
var RandReader = rand.Reader

const idempotencyTokenFillTag = `idempotencyToken`

// CanSetIdempotencyToken returns true if the struct field should be
// automatically populated with a Idempotency token.
//
// Only *string and string type fields that are tagged with idempotencyToken
// which are not already set can be auto filled.
func CanSetIdempotencyToken(v reflect.Value, f reflect.StructField) bool {
	switch u := v.Interface().(type) {
	// To auto fill an Idempotency token the field must be a string,
	// tagged for auto fill, and have a zero value.
	case *string:
		return u == nil && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
	case string:
		return len(u) == 0 && len(f.Tag.Get(idempotencyTokenFillTag)) != 0
	}

	return false
}

// GetIdempotencyToken returns a randomly generated idempotency token.
func GetIdempotencyToken() string {
	b := make([]byte, 16)
	RandReader.Read(b)

	return UUIDVersion4(b)
}

// SetIdempotencyToken will set the value provided with a Idempotency Token.
// Given that the value can be set. Will panic if value is not setable.
func SetIdempotencyToken(v reflect.Value) {
	if v.Kind() == reflect.Ptr {
		if v.IsNil() && v.CanSet() {
			v.Set(reflect.New(v.Type().Elem()))
		}
		v = v.Elem()
	}
	v = reflect.Indirect(v)

	if !v.CanSet() {
		panic(fmt.Sprintf("unable to set idempotnecy token %v", v))
	}

	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		// TODO handle error
		return
	}

	v.Set(reflect.ValueOf(UUIDVersion4(b)))
}

// UUIDVersion4 returns a Version 4 random UUID from the byte slice provided
func UUIDVersion4(u []byte) string {
	// https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_.28random.29
	// 13th character is "4"
	u[6] = (u[6] | 0x40) & 0x4F
	// 17th character is "8", "9", "a", or "b"
	u[8] = (u[8] | 0x80) & 0xBF

	return fmt.Sprintf(`%X-%X-%X-%X-%X`, u[0:4], u[4:6], u[6:8], u[8:10], u[10:])
}
