// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package parserprovider

import (
	"bytes"
	"fmt"
	"strings"

	"github.com/spf13/viper"

	"go.opentelemetry.io/collector/config"
)

const setFlagFileType = "properties"

type setFlagProvider struct {
	base ParserProvider
}

// NewSetFlag returns a config.ParserProvider, that wraps a "base" config.ParserProvider, then
// overrides properties from set flag(s) in the loaded Parser.
//
// The implementation reads set flag(s) from the cmd and concatenates them as a "properties" file.
// Then the properties file is read and properties are set to the loaded Parser.
func NewSetFlag(base ParserProvider) ParserProvider {
	return &setFlagProvider{
		base: base,
	}
}

func (sfl *setFlagProvider) Get() (*config.Parser, error) {
	flagProperties := getSetFlag()
	if len(flagProperties) == 0 {
		return sfl.base.Get()
	}

	b := &bytes.Buffer{}
	for _, property := range flagProperties {
		property = strings.TrimSpace(property)
		if _, err := fmt.Fprintf(b, "%s\n", property); err != nil {
			return nil, err
		}
	}
	viperFlags := viper.NewWithOptions(viper.KeyDelimiter(config.KeyDelimiter))
	viperFlags.SetConfigType(setFlagFileType)
	if err := viperFlags.ReadConfig(b); err != nil {
		return nil, fmt.Errorf("failed to read set flag config: %v", err)
	}

	cp, err := sfl.base.Get()
	if err != nil {
		return nil, err
	}

	// Viper implementation of v.MergeConfig(io.Reader) or v.MergeConfigMap(map[string]interface)
	// does not work properly.  This is b/c if it attempts to merge into a nil object it will fail here
	// https://github.com/spf13/viper/blob/3826be313591f83193f048520482a7b3cf17d506/viper.go#L1709

	// The workaround is to call v.Set(string, interface) on all root properties from the config file
	// this will correctly preserve the original config and set them up for viper to overlay them with
	// the --set params.  It should also be noted that setting the root keys is important.  This is
	// b/c the viper .AllKeys() method does not return empty objects.
	// For instance with the following yaml structure:
	// a:
	//   b:
	//   c: {}
	//
	// viper.AllKeys() would only return a.b, but not a.c.  However otel expects {} to behave
	// the same as nil object in its config file.  Therefore we extract and set the root keys only
	// to catch both a.b and a.c.

	rootKeys := map[string]struct{}{}
	for _, k := range viperFlags.AllKeys() {
		keys := strings.Split(k, config.KeyDelimiter)
		if len(keys) > 0 {
			rootKeys[keys[0]] = struct{}{}
		}
	}

	for k := range rootKeys {
		cp.Set(k, cp.Get(k))
	}

	// now that we've copied the config into the viper "overrides" copy the --set flags
	// as well
	for _, k := range viperFlags.AllKeys() {
		cp.Set(k, viperFlags.Get(k))
	}

	return cp, nil
}
