package accesslog

import (
	"net/http"
	"net/url"
)

type (
	FieldConfig struct {
		Default FieldMode            `json:"default" validate:"oneof=keep drop redact"`
		Config  map[string]FieldMode `json:"config" validate:"dive,oneof=keep drop redact"`
	}
	FieldMode string
)

const (
	FieldModeKeep   FieldMode = "keep"
	FieldModeDrop   FieldMode = "drop"
	FieldModeRedact FieldMode = "redact"

	RedactedValue = "REDACTED"
)

func processMap[V any](cfg *FieldConfig, m map[string]V, redactedV V) map[string]V {
	if len(cfg.Config) == 0 {
		switch cfg.Default {
		case FieldModeKeep:
			return m
		case FieldModeDrop:
			return nil
		case FieldModeRedact:
			redacted := make(map[string]V)
			for k := range m {
				redacted[k] = redactedV
			}
			return redacted
		}
	}

	if len(m) == 0 {
		return m
	}

	newMap := make(map[string]V, len(m))
	for k := range m {
		var mode FieldMode
		var ok bool
		if mode, ok = cfg.Config[k]; !ok {
			mode = cfg.Default
		}
		switch mode {
		case FieldModeKeep:
			newMap[k] = m[k]
		case FieldModeRedact:
			newMap[k] = redactedV
		}
	}
	return newMap
}

func processSlice[V any, VReturn any](cfg *FieldConfig, s []V, getKey func(V) string, convert func(V) VReturn, redact func(V) VReturn) map[string]VReturn {
	if len(s) == 0 ||
		len(cfg.Config) == 0 && cfg.Default == FieldModeDrop {
		return nil
	}
	newMap := make(map[string]VReturn, len(s))
	for _, v := range s {
		var mode FieldMode
		var ok bool
		k := getKey(v)
		if mode, ok = cfg.Config[k]; !ok {
			mode = cfg.Default
		}
		switch mode {
		case FieldModeKeep:
			newMap[k] = convert(v)
		case FieldModeRedact:
			newMap[k] = redact(v)
		}
	}
	return newMap
}

func (cfg *FieldConfig) ProcessHeaders(headers http.Header) http.Header {
	return processMap(cfg, headers, []string{RedactedValue})
}

func (cfg *FieldConfig) ProcessQuery(q url.Values) url.Values {
	return processMap(cfg, q, []string{RedactedValue})
}

func (cfg *FieldConfig) ProcessCookies(cookies []*http.Cookie) map[string]string {
	return processSlice(cfg, cookies,
		func(c *http.Cookie) string {
			return c.Name
		},
		func(c *http.Cookie) string {
			return c.Value
		},
		func(c *http.Cookie) string {
			return RedactedValue
		})
}