// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package internal

// https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules

import (
	"encoding/json"
	"strings"
)

const (
	placeholder = "*"
	separator   = "/"
)

type segmentRule struct {
	Prefix   string   `json:"prefix"`
	Terms    []string `json:"terms"`
	TermsMap map[string]struct{}
}

// segmentRules is keyed by each segmentRule's Prefix field with any trailing
// slash removed.
type segmentRules map[string]*segmentRule

func buildTermsMap(terms []string) map[string]struct{} {
	m := make(map[string]struct{}, len(terms))
	for _, t := range terms {
		m[t] = struct{}{}
	}
	return m
}

func (rules *segmentRules) UnmarshalJSON(b []byte) error {
	var raw []*segmentRule

	if err := json.Unmarshal(b, &raw); nil != err {
		return err
	}

	rs := make(map[string]*segmentRule)

	for _, rule := range raw {
		prefix := strings.TrimSuffix(rule.Prefix, "/")
		if len(strings.Split(prefix, "/")) != 2 {
			// TODO
			// Warn("invalid segment term rule prefix",
			// 	{"prefix": rule.Prefix})
			continue
		}

		if nil == rule.Terms {
			// TODO
			// Warn("segment term rule has missing terms",
			// 	{"prefix": rule.Prefix})
			continue
		}

		rule.TermsMap = buildTermsMap(rule.Terms)

		rs[prefix] = rule
	}

	*rules = rs
	return nil
}

func (rule *segmentRule) apply(name string) string {
	if !strings.HasPrefix(name, rule.Prefix) {
		return name
	}

	s := strings.TrimPrefix(name, rule.Prefix)

	leadingSlash := ""
	if strings.HasPrefix(s, separator) {
		leadingSlash = separator
		s = strings.TrimPrefix(s, separator)
	}

	if "" != s {
		segments := strings.Split(s, separator)

		for i, segment := range segments {
			_, allowed := rule.TermsMap[segment]
			if allowed {
				segments[i] = segment
			} else {
				segments[i] = placeholder
			}
		}

		segments = collapsePlaceholders(segments)
		s = strings.Join(segments, separator)
	}

	return rule.Prefix + leadingSlash + s
}

func (rules segmentRules) apply(name string) string {
	if nil == rules {
		return name
	}

	rule, ok := rules[firstTwoSegments(name)]
	if !ok {
		return name
	}

	return rule.apply(name)
}

func firstTwoSegments(name string) string {
	firstSlashIdx := strings.Index(name, separator)
	if firstSlashIdx == -1 {
		return name
	}

	secondSlashIdx := strings.Index(name[firstSlashIdx+1:], separator)
	if secondSlashIdx == -1 {
		return name
	}

	return name[0 : firstSlashIdx+secondSlashIdx+1]
}

func collapsePlaceholders(segments []string) []string {
	j := 0
	prevStar := false
	for i := 0; i < len(segments); i++ {
		segment := segments[i]
		if placeholder == segment {
			if prevStar {
				continue
			}
			segments[j] = placeholder
			j++
			prevStar = true
		} else {
			segments[j] = segment
			j++
			prevStar = false
		}
	}
	return segments[0:j]
}
