package identity

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"errors"
	"strings"
	"unicode/utf8"
)

var errDecryptSecret = errors.New("decrypt secret failed")

func DecryptOptional(value *string, secret string) *string {
	if value == nil || strings.TrimSpace(*value) == "" {
		return nil
	}
	plain, err := decryptSecret(*value, secret)
	if err != nil {
		return value
	}
	return &plain
}

func MaskName(value *string) *string {
	if value == nil {
		return nil
	}
	runes := []rune(strings.TrimSpace(*value))
	if len(runes) == 0 {
		return nil
	}
	if len(runes) == 1 {
		out := "*"
		return &out
	}
	out := string(runes[0]) + strings.Repeat("*", len(runes)-1)
	return &out
}

func MaskIDCard(value *string) *string {
	if value == nil {
		return nil
	}
	text := strings.ToUpper(strings.TrimSpace(*value))
	if len(text) == 18 {
		out := text[:6] + "********" + text[14:]
		return &out
	}
	if len(text) > 8 {
		out := text[:4] + "****" + text[len(text)-4:]
		return &out
	}
	out := "****"
	return &out
}

func EncryptOptional(value string, secret string) (*string, error) {
	text := strings.TrimSpace(value)
	if text == "" {
		return nil, nil
	}
	if _, err := decryptSecret(text, secret); err == nil {
		return &text, nil
	}
	encrypted, err := encryptSecret(text, secret)
	if err != nil {
		return nil, err
	}
	return &encrypted, nil
}

func DeterministicHash(value string, secret string) *string {
	text := strings.ToUpper(strings.TrimSpace(value))
	if text == "" {
		return nil
	}
	mac := hmac.New(sha256.New, []byte(secret))
	_, _ = mac.Write([]byte(text))
	out := hex.EncodeToString(mac.Sum(nil))
	return &out
}

func encryptSecret(plaintext string, secret string) (string, error) {
	hash := sha256.Sum256([]byte(secret))
	block, err := aes.NewCipher(hash[:])
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}
	sealed := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.StdEncoding.EncodeToString(sealed), nil
}

func decryptSecret(token string, secret string) (string, error) {
	raw, err := base64.StdEncoding.DecodeString(token)
	if err != nil {
		return "", err
	}
	if len(raw) < 13 {
		return "", errDecryptSecret
	}
	hash := sha256.Sum256([]byte(secret))
	block, err := aes.NewCipher(hash[:])
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}
	nonceLen := gcm.NonceSize()
	if len(raw) <= nonceLen {
		return "", errDecryptSecret
	}
	plain, err := gcm.Open(nil, raw[:nonceLen], raw[nonceLen:], nil)
	if err != nil {
		return "", err
	}
	if !utf8.Valid(plain) {
		return "", errDecryptSecret
	}
	return string(plain), nil
}
