package accountcli

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"reflect"
	"sort"
	"strings"
	"time"

	"github.com/go-logr/logr"
	"github.com/patrickmn/go-cache"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
)

// Authenticator holds the AK, SK, and a cache for used nonces
type Authenticator struct {
	AK         string
	SK         string
	NonceStore *cache.Cache // Using a TTL cache for nonce storage
	Logger     logr.Logger
}

// NewClientAuthenticator creates a new Authenticator for client
func NewClientAuthenticator(ak, sk string, logger logr.Logger) *Authenticator {
	return &Authenticator{AK: ak, SK: sk, Logger: logger}
}

// SignRequest signs the request with AK/SK, timestamp, and nonce
func (a *Authenticator) SignRequest(req *http.Request) {
	timestamp := time.Now().UTC().Format(time.RFC3339)
	nonce := generateNonce()
	signature := a.generateSignature(req, timestamp, nonce)

	req.Header.Set("X-AK", a.AK)
	req.Header.Set("X-Timestamp", timestamp)
	req.Header.Set("X-Nonce", nonce)
	req.Header.Set("X-Signature", signature)
}

// ValidateRequest validates the incoming request using AK/SK, timestamp, and nonce
func (a *Authenticator) ValidateRequest(ctx context.Context, req *http.Request, akMap map[string]string) bool {
	ak := req.Header.Get("X-AK")
	timestamp := req.Header.Get("X-Timestamp")
	nonce := req.Header.Get("X-Nonce")
	signature := req.Header.Get("X-Signature")

	val, ok := akMap[ak]
	if !ok {
		a.Logger.Info("ak is not exist", "ak", ak)
		return false
	}
	a.AK = ak
	a.SK = val
	// Check if the request is within 5 minutes of the current time
	requestTime, err := time.Parse(time.RFC3339, timestamp)
	if err != nil || time.Since(requestTime) > 2*time.Minute {
		a.Logger.Info("timestamp is not valid, > 2 minutes", "timestamp", timestamp)
		return false
	}

	// Check if the nonce has been used
	if _, found := a.NonceStore.Get(nonce); found {
		return false
	}

	// Store the nonce
	a.NonceStore.Set(nonce, requestTime, cache.DefaultExpiration)

	// Generate the expected signature
	expectedSignature := a.generateSignature(req, timestamp, nonce)

	return hmac.Equal([]byte(signature), []byte(expectedSignature))
}

// generateSignature generates a signature using HMAC-SHA256
func (a *Authenticator) generateSignature(req *http.Request, timestamp, nonce string) string {
	method := req.Method
	path := req.URL.Path

	var params string
	if method == http.MethodGet {
		params = getSortedParams(req.URL.Query())
	} else {
		if req.Body != nil {
			bodyParams := getBodyParams(req)
			params = getSortedParams(bodyParams)
		}
	}

	message := method + path + params + timestamp + nonce
	mac := hmac.New(sha256.New, []byte(a.SK))
	mac.Write([]byte(message))
	return hex.EncodeToString(mac.Sum(nil))
}

// getSortedParams returns a sorted query string or body parameters
func getSortedParams(values url.Values) string {
	var keys []string
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	var params []string
	for _, key := range keys {
		params = append(params, key+"="+values.Get(key))
	}
	return strings.Join(params, "&")
}

// getBodyParams extracts parameters from the request body and returns them as url.Values
func getBodyParams(req *http.Request) url.Values {
	var bodyParams map[string]interface{}
	body, err := io.ReadAll(req.Body)
	if err != nil {
		return nil
	}

	// Restore the request body to its original state
	req.Body = io.NopCloser(bytes.NewBuffer(body))

	if err := json.Unmarshal(body, &bodyParams); err != nil {
		return nil
	}

	values := url.Values{}
	for key, value := range bodyParams {
		values.Set(key, valueToString(value))
	}
	return values
}

// valueToString converts interface{} to string
// valueToString converts interface{} to string
func valueToString(v interface{}) string {
	val := reflect.ValueOf(v)
	switch val.Kind() {
	case reflect.String:
		return val.String()
	case reflect.Bool:
		return fmt.Sprintf("%t", val.Bool())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return fmt.Sprintf("%d", val.Int())
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return fmt.Sprintf("%d", val.Uint())
	case reflect.Float32, reflect.Float64:
		return fmt.Sprintf("%f", val.Float())
	case reflect.Slice, reflect.Array:
		var result []string
		for i := 0; i < val.Len(); i++ {
			result = append(result, valueToString(val.Index(i).Interface()))
		}
		return strings.Join(result, ",")
	case reflect.Map:
		var result []string
		for _, key := range val.MapKeys() {
			result = append(result, fmt.Sprintf("%s:%s", valueToString(key.Interface()), valueToString(val.MapIndex(key).Interface())))
		}
		return "{" + strings.Join(result, ",") + "}"
	default:
		jsonValue, err := json.Marshal(v)
		if err != nil {
			return ""
		}
		return string(jsonValue)
	}
}

// generateNonce generates a random nonce
func generateNonce() string {
	return container.RandomString(14)
}

func signMiddleware(ak, sk string, logger logr.Logger, req *http.Request) error {
	authenticator := NewClientAuthenticator(ak, sk, logger)
	authenticator.SignRequest(req)
	return nil
}
