// Package logger add by blacknc <blacknc@163.com> at 2024/03/13 15:05:49
package logger

import (
	"context"
	"dataservices/commons/contextvar"
	comerrs "dataservices/commons/errs"
	"dataservices/commons/utils"
	"maps"
	"reflect"
	"strings"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

const redactSalt = "7e8b7a3a7bd9c3e5f6a4b2c1d0e9f" // random salt to prevent hash collisions with real keys

func LogWithError(ctx context.Context, err error, msg string, fields ...zap.Field) {
	logLevel := zap.ErrorLevel
	if err == nil {
		logLevel = zap.InfoLevel
	} else if comerrs.IsClientErr(err) ||
		utils.IsTestUser(contextvar.AccountID(ctx)) ||
		strings.Contains(err.Error(), "context deadline exceeded") ||
		strings.Contains(err.Error(), "context canceled") {
		logLevel = zap.WarnLevel
	}
	fields = append(fields, zap.Error(err))
	contextvar.Logger(ctx).Log(logLevel, msg, fields...)
}

func Info(ctx context.Context, msg string, fields ...zap.Field) {
	contextvar.Logger(ctx).Info(msg, fields...)
}

func Warn(ctx context.Context, msg string, fields ...zap.Field) {
	contextvar.Logger(ctx).Warn(msg, fields...)
}

func Error(ctx context.Context, msg string, fields ...zap.Field) {
	contextvar.Logger(ctx).Error(msg, fields...)
}

func Log(ctx context.Context, level zapcore.Level, msg string, fields ...zap.Field) {
	contextvar.Logger(ctx).Log(level, msg, fields...)
}

// Redact logs value under key, redacting Credential.Key if present.
// It copies value before redacting so the original is never mutated.
// Both value and its Credential field may be pointers or plain structs.
func Redact(key string, value interface{}) zap.Field {
	if value == nil {
		return zap.Any(key, nil)
	}
	if params, ok := value.(map[string]string); ok {
		if token, ok := params["auth0_token"]; ok {
			redactParam := maps.Clone(params)
			redactParam["auth0_token"] = redactString(token)
			return zap.Any(key, redactParam)
		} else {
			return zap.Any(key, params)
		}
	}

	v := reflect.ValueOf(value)
	isPtr := v.Kind() == reflect.Ptr
	if isPtr {
		if v.IsNil() {
			return zap.Any(key, value)
		}
		v = v.Elem()
	}
	if v.Kind() == reflect.Map {
	}
	if v.Kind() != reflect.Struct {
		return zap.Any(key, value)
	}

	credField := v.FieldByName("Credential")
	if !credField.IsValid() {
		return zap.Any(key, value)
	}

	// Resolve Credential to its underlying struct (deref pointer if needed).
	credIsPtr := credField.Kind() == reflect.Ptr
	credVal := credField
	if credIsPtr {
		if credField.IsNil() {
			return zap.Any(key, value)
		}
		credVal = credField.Elem()
	}
	if credVal.Kind() != reflect.Struct {
		return zap.Any(key, value)
	}

	keyField := credVal.FieldByName("Key")
	if !keyField.IsValid() || keyField.Kind() != reflect.String || keyField.String() == "" {
		return zap.Any(key, value)
	}

	// Copy the outer struct so we never mutate the original.
	outerCopy := reflect.New(v.Type()).Elem()
	outerCopy.Set(v)

	orig := keyField.String()
	redactedValue := redactString(orig)

	if credIsPtr {
		// The copy shares the original Credential pointer; make an independent copy.
		credCopy := reflect.New(credVal.Type()).Elem()
		credCopy.Set(credVal)
		credCopy.FieldByName("Key").SetString(redactedValue)
		outerCopy.FieldByName("Credential").Set(credCopy.Addr())
	} else {
		// Credential was copied by value; redact the Key directly in the copy.
		outerCopy.FieldByName("Credential").FieldByName("Key").SetString(redactedValue)
	}

	if isPtr {
		return zap.Any(key, outerCopy.Addr().Interface())
	}
	return zap.Any(key, outerCopy.Interface())
}

func redactString(s string) string {
	if s == "" {
		return s
	}
	sum := utils.Md5WithDefaultSalt(s)
	return "<<redacted>>" + sum
}
