// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 config

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
)

func ExampleConfigTool_readValue() {
	tmpDir, _ := os.MkdirTemp("", "ops")
	defer os.RemoveAll(tmpDir)

	opsRootPath := filepath.Join(tmpDir, "opsroot.json")
	configPath := filepath.Join(tmpDir, "config.json")
	cm, _ := NewConfigMapBuilder().WithOpsRoot(opsRootPath).WithConfigJson(configPath).Build()

	os.Args = []string{"config", "FOO=bar"}
	err := ConfigTool(cm)
	if err != nil {
		fmt.Println("error:", err)
	}

	os.Args = []string{"config", "FOO"}
	err = ConfigTool(cm)
	if err != nil {
		fmt.Println("error:", err)
	}

	// nested key
	os.Args = []string{"config", "NESTED_VAL=val"}
	err = ConfigTool(cm)
	if err != nil {
		fmt.Println("error:", err)
	}

	os.Args = []string{"config", "NESTED_VAL"}
	err = ConfigTool(cm)
	if err != nil {
		fmt.Println("error:", err)
	}
	// Output:
	// bar
	// val
}

func TestConfigTool(t *testing.T) {
	readConfigJson := func(path string) (map[string]interface{}, error) {
		return readConfig(filepath.Join(path, "config.json"), fromConfigJson)
	}

	buildCM := func(nv string, c string) ConfigMap {
		cm, _ := NewConfigMapBuilder().WithConfigJson(c).WithOpsRoot(nv).Build()
		return cm
	}
	t.Run("new config.json", func(t *testing.T) {
		tmpDir := t.TempDir()

		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{
			"foo": "bar",
		}

		require.Equal(t, want, got)
	})

	t.Run("write values on existing config.json", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		os.Args = []string{"config", "BAR=baz"}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{
			"foo": "bar",
			"bar": "baz",
		}

		require.Equal(t, want, got)
	})

	t.Run("write existing value is overridden", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		os.Args = []string{"config", "FOO=new"}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{
			"foo": "new",
		}

		require.Equal(t, want, got)
	})

	t.Run("write existing key object is merged", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO_BAR=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		os.Args = []string{"config", "FOO_BAZ=baz"}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{
			"foo": map[string]interface{}{
				"bar": "bar",
				"baz": "baz",
			},
		}

		require.Equal(t, want, got)
	})

	t.Run("write empty string to disable key", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO_BAR=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		os.Args = []string{"config", "FOO_BAR=\"\""}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{
			"foo": map[string]interface{}{
				"bar": "",
			},
		}

		require.Equal(t, want, got)
	})

	t.Run("remove existing key", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO=bar"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		os.Args = []string{"config", "-r", "FOO"}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{}

		require.Equal(t, want, got)
	})

	t.Run("remove nested key object", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "FOO_BAR=bar", "FOO_BAZ=baz"}
		err := ConfigTool(cm)
		require.NoError(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)
		want := map[string]interface{}{
			"foo": map[string]interface{}{
				"bar": "bar",
				"baz": "baz",
			},
		}
		require.Equal(t, want, got)

		os.Args = []string{"config", "-r", "FOO_BAR"}
		err = ConfigTool(cm)
		require.NoError(t, err)

		got, err = readConfigJson(tmpDir)
		require.NoError(t, err)

		want = map[string]interface{}{
			"foo": map[string]interface{}{
				"baz": "baz",
			},
		}

		require.Equal(t, want, got)
	})

	t.Run("remove non-existing key", func(t *testing.T) {
		tmpDir := t.TempDir()
		opsRootPath := filepath.Join(tmpDir, "opsroot.json")
		configPath := filepath.Join(tmpDir, "config.json")
		cm := buildCM(opsRootPath, configPath)

		os.Args = []string{"config", "-r", "FOO"}
		err := ConfigTool(cm)
		require.Error(t, err)

		got, err := readConfigJson(tmpDir)
		require.NoError(t, err)

		want := map[string]interface{}{}

		require.Equal(t, want, got)
	})
}

func Test_buildKeyValueMap(t *testing.T) {
	testCases := []struct {
		name  string
		input []string
		want  keyValues
		err   error
	}{
		{
			name:  "Empty string",
			input: []string{},
			want:  nil,
			err:   fmt.Errorf("no key-value pairs provided"),
		},
		{
			name:  "Single key-value pair",
			input: []string{"foo=bar"},
			want:  keyValues{"foo": "bar"},
			err:   nil,
		},
		{
			name:  "Multiple key-value pairs",
			input: []string{"foo=bar", "baz=qux"},
			want:  keyValues{"foo": "bar", "baz": "qux"},
			err:   nil,
		},
		{
			name:  "Invalid key-value pair",
			input: []string{"foo"},
			want:  nil,
			err:   fmt.Errorf("invalid key-value pair: %q", "foo"),
		},
		{
			name:  "Invalid key-value pair",
			input: []string{"foo="},
			want:  nil,
			err:   fmt.Errorf("invalid key-value pair: %q", "foo="),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := buildInputKVMap(tc.input)

			require.Equal(t, tc.err, err)
			require.Equal(t, tc.want, got)
		})
	}
}
