package redact

import (
	"encoding/json"
	"reflect"
	"strings"
)

var PasswordFields = []string{"password"}

func RedactKeysToJSON(redacted []string, req interface{}) ([]byte, error) {
	mapData, err := AsMapRedactKeys(redacted, req)
	if err != nil {
		return nil, err
	}
	b, err := json.Marshal(mapData)
	// This error check is currently not necessary
	// The current version of json.Marshal will not return both a result and an error
	// But this might not be the case for a different version
	if err != nil {
		return nil, err
	}
	return b, nil
}

// TODO: this is inefficient because it marshals and unmarshals
// TODO: this could lose accuracy by converting an int to a float
func interfaceToMap(data interface{}) (map[string]interface{}, error) {
	dataBytes, err := json.Marshal(data)
	if err != nil {
		return nil, err
	}
	mapData := make(map[string]interface{})
	err = json.Unmarshal(dataBytes, &mapData)
	if err != nil {
		return nil, err
	}
	return mapData, nil
}

func RedactKeys(fieldNameContains []string, mapData map[string]interface{}) map[string]interface{} {
	fncLower := []string{}
	for _, banned := range fieldNameContains {
		fncLower = append(fncLower, strings.ToLower(banned))
	}

	for k, v := range mapData {
		skip := false
		kl := strings.ToLower(k)
		for _, banned := range fncLower {
			if strings.Contains(kl, banned) {
				// log.Info("redacted " + k)
				t := reflect.TypeOf(v)
				if t.Kind() == reflect.String {
					mapData[k] = "redacted"
				} else {
					z := reflect.Zero(t)
					if z.CanInterface() {
						mapData[k] = z.Interface()
					} else {
						delete(mapData, k)
					}
				}
				skip = true
				break
			}
		}
		if skip {
			continue
		}
		// log.Info("keeping " + k)
		if mapping, ok := v.(map[string]interface{}); ok {
			mapData[k] = RedactKeys(fieldNameContains, mapping)
		}
		if mappings, ok := v.([]map[string]interface{}); ok {
			for _, mapping := range mappings {
				newMappings := []map[string]interface{}{}
				newMappings = append(newMappings, RedactKeys(fieldNameContains, mapping))
				mapData[k] = newMappings
			}
		}
	}

	return mapData
}

// The given interface must be able to be JSON marshaled to a map
func AsMapRedactKeys(fieldNameContains []string, req interface{}) (map[string]interface{}, error) {
	mapping, err := interfaceToMap(req)
	if err != nil {
		return nil, err
	}
	return RedactKeys(fieldNameContains, mapping), nil
}
