package carddata

import (
	"bytes"
	"encoding/json"
	"strings"
	"unicode"
)

const Redacted = "[redacted-card-data]"

func RedactJSON(raw []byte) []byte {
	if len(bytes.TrimSpace(raw)) == 0 {
		return []byte(`{}`)
	}

	var value any
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	if err := decoder.Decode(&value); err != nil {
		return []byte(`{}`)
	}

	redactValue(value, nil)

	out, err := json.Marshal(value)
	if err != nil {
		return []byte(`{}`)
	}
	return out
}

func redactValue(value any, path []string) {
	switch typed := value.(type) {
	case map[string]any:
		cardContext := hasCardContext(path, typed)
		for key, nested := range typed {
			if isSensitiveCardKey(key, cardContext) {
				typed[key] = Redacted
				continue
			}
			redactValue(nested, append(path, key))
		}
	case []any:
		for _, nested := range typed {
			redactValue(nested, path)
		}
	}
}

func isSensitiveCardKey(key string, cardContext bool) bool {
	normalized := normalizeKey(key)
	switch normalized {
	case "pan", "cardpan", "primaryaccountnumber", "cardnumber", "cardno":
		return true
	case "cvv", "cvc", "cvv2", "cvc2", "securitycode", "cardsecuritycode", "verificationcode", "verificationvalue", "cardverificationvalue":
		return true
	case "track1", "track2", "trackdata", "magstripedata":
		return true
	case "number":
		return cardContext
	default:
		return false
	}
}

func hasCardContext(path []string, object map[string]any) bool {
	for _, key := range path {
		normalized := normalizeKey(key)
		if strings.Contains(normalized, "card") || strings.Contains(normalized, "paymentinstrument") {
			return true
		}
	}

	for key := range object {
		switch normalizeKey(key) {
		case "last4", "expmonth", "expyear", "expirationmonth", "expirationyear", "expiry", "cardholdername", "cardtype":
			return true
		}
	}

	return false
}

func normalizeKey(key string) string {
	var builder strings.Builder
	for _, r := range strings.ToLower(key) {
		if unicode.IsLetter(r) || unicode.IsDigit(r) {
			builder.WriteRune(r)
		}
	}
	return builder.String()
}
