// Copyright 2021 The Hugo Authors. All rights reserved.
//
// 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 config

import (
	"context"
	"errors"
	"fmt"
	"slices"
	"strconv"
	"strings"
	"testing"

	"github.com/gohugoio/hugo/common/para"

	"github.com/gohugoio/hugo/common/maps"

	qt "github.com/frankban/quicktest"
)

func TestDefaultConfigProvider(t *testing.T) {
	c := qt.New(t)

	c.Run("Set and get", func(c *qt.C) {
		cfg := New()
		var k string
		var v any

		k, v = "foo", "bar"
		cfg.Set(k, v)
		c.Assert(cfg.Get(k), qt.Equals, v)
		c.Assert(cfg.Get(strings.ToUpper(k)), qt.Equals, v)
		c.Assert(cfg.GetString(k), qt.Equals, v)

		k, v = "foo", 42
		cfg.Set(k, v)
		c.Assert(cfg.Get(k), qt.Equals, v)
		c.Assert(cfg.GetInt(k), qt.Equals, v)

		c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
			"foo": 42,
		})
	})

	c.Run("Set and get map", func(c *qt.C) {
		cfg := New()

		cfg.Set("foo", map[string]any{
			"bar": "baz",
		})

		c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
			"bar": "baz",
		})

		c.Assert(cfg.GetStringMap("foo"), qt.DeepEquals, map[string]any{"bar": string("baz")})
		c.Assert(cfg.GetStringMapString("foo"), qt.DeepEquals, map[string]string{"bar": string("baz")})
	})

	c.Run("Set and get nested", func(c *qt.C) {
		cfg := New()

		cfg.Set("a", map[string]any{
			"B": "bv",
		})
		cfg.Set("a.c", "cv")

		c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
			"b": "bv",
			"c": "cv",
		})
		c.Assert(cfg.Get("a.c"), qt.Equals, "cv")

		cfg.Set("b.a", "av")
		c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
			"a": "av",
		})

		cfg.Set("b", map[string]any{
			"b": "bv",
		})

		c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
			"a": "av",
			"b": "bv",
		})

		cfg = New()

		cfg.Set("a", "av")

		cfg.Set("", map[string]any{
			"a": "av2",
			"b": "bv2",
		})

		c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
			"a": "av2",
			"b": "bv2",
		})

		cfg = New()

		cfg.Set("a", "av")

		cfg.Set("", map[string]any{
			"b": "bv2",
		})

		c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
			"a": "av",
			"b": "bv2",
		})

		cfg = New()

		cfg.Set("", map[string]any{
			"foo": map[string]any{
				"a": "av",
			},
		})

		cfg.Set("", map[string]any{
			"foo": map[string]any{
				"b": "bv2",
			},
		})

		c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
			"a": "av",
			"b": "bv2",
		})
	})

	c.Run("Merge default strategy", func(c *qt.C) {
		cfg := New()

		cfg.Set("a", map[string]any{
			"B": "bv",
		})

		cfg.Merge("a", map[string]any{
			"B": "bv2",
			"c": "cv2",
		})

		c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
			"b": "bv",
			"c": "cv2",
		})

		cfg = New()

		cfg.Set("a", "av")

		cfg.Merge("", map[string]any{
			"a": "av2",
			"b": "bv2",
		})

		c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
			"a": "av",
		})
	})

	c.Run("Merge shallow", func(c *qt.C) {
		cfg := New()

		cfg.Set("a", map[string]any{
			"_merge": "shallow",
			"B":      "bv",
			"c": map[string]any{
				"b": "bv",
			},
		})

		cfg.Merge("a", map[string]any{
			"c": map[string]any{
				"d": "dv2",
			},
			"e": "ev2",
		})

		c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
			"e":      "ev2",
			"_merge": maps.ParamsMergeStrategyShallow,
			"b":      "bv",
			"c": maps.Params{
				"b": "bv",
			},
		})
	})

	// Issue #8679
	c.Run("Merge typed maps", func(c *qt.C) {
		for _, left := range []any{
			map[string]string{
				"c": "cv1",
			},
			map[string]any{
				"c": "cv1",
			},
			map[any]any{
				"c": "cv1",
			},
		} {
			cfg := New()

			cfg.Set("", map[string]any{
				"b": left,
			})

			cfg.Merge("", maps.Params{
				"b": maps.Params{
					"c": "cv2",
					"d": "dv2",
				},
			})

			c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
				"b": maps.Params{
					"c": "cv1",
					"d": "dv2",
				},
			})
		}

		for _, left := range []any{
			map[string]string{
				"b": "bv1",
			},
			map[string]any{
				"b": "bv1",
			},
			map[any]any{
				"b": "bv1",
			},
		} {
			for _, right := range []any{
				map[string]string{
					"b": "bv2",
					"c": "cv2",
				},
				map[string]any{
					"b": "bv2",
					"c": "cv2",
				},
				map[any]any{
					"b": "bv2",
					"c": "cv2",
				},
			} {
				cfg := New()

				cfg.Set("a", left)

				cfg.Merge("a", right)

				c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
					"a": maps.Params{
						"b": "bv1",
						"c": "cv2",
					},
				})
			}
		}
	})

	// Issue #8701
	c.Run("Prevent _merge only maps", func(c *qt.C) {
		cfg := New()

		cfg.Set("", map[string]any{
			"B": "bv",
		})

		cfg.Merge("", map[string]any{
			"c": map[string]any{
				"_merge": "shallow",
				"d":      "dv2",
			},
		})

		c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
			"b": "bv",
		})
	})

	c.Run("IsSet", func(c *qt.C) {
		cfg := New()

		cfg.Set("a", map[string]any{
			"B": "bv",
		})

		c.Assert(cfg.IsSet("A"), qt.IsTrue)
		c.Assert(cfg.IsSet("a.b"), qt.IsTrue)
		c.Assert(cfg.IsSet("z"), qt.IsFalse)
	})

	c.Run("Para", func(c *qt.C) {
		cfg := New()
		p := para.New(4)
		r, _ := p.Start(context.Background())

		setAndGet := func(k string, v int) error {
			vs := strconv.Itoa(v)
			cfg.Set(k, v)
			err := errors.New("get failed")
			if cfg.Get(k) != v {
				return err
			}
			if cfg.GetInt(k) != v {
				return err
			}
			if cfg.GetString(k) != vs {
				return err
			}
			if !cfg.IsSet(k) {
				return err
			}
			return nil
		}

		for i := range 20 {
			r.Run(func() error {
				const v = 42
				k := fmt.Sprintf("k%d", i)
				if err := setAndGet(k, v); err != nil {
					return err
				}

				m := maps.Params{
					"new": 42,
				}

				cfg.Merge("", m)

				return nil
			})
		}

		c.Assert(r.Wait(), qt.IsNil)
	})

	c.Run("GetBool", func(c *qt.C) {
		cfg := New()

		var k string
		var v bool

		k, v = "foo", true

		cfg.Set(k, v)
		c.Assert(cfg.Get(k), qt.Equals, v)
		c.Assert(cfg.GetBool(k), qt.Equals, v)
	})

	c.Run("GetParams", func(c *qt.C) {
		cfg := New()
		k := "foo"

		cfg.Set(k, maps.Params{k: true})
		c.Assert(cfg.GetParams(k), qt.DeepEquals, maps.Params{
			k: true,
		})

		c.Assert(cfg.GetParams("bar"), qt.IsNil)
	})

	c.Run("Keys", func(c *qt.C) {
		cfg := New()
		k := "foo"
		k2 := "bar"

		cfg.Set(k, maps.Params{k: struct{}{}})
		cfg.Set(k2, maps.Params{k2: struct{}{}})

		c.Assert(len(cfg.Keys()), qt.Equals, 2)

		got := cfg.Keys()
		slices.Sort(got)

		want := []string{k, k2}
		slices.Sort(want)

		c.Assert(got, qt.DeepEquals, want)
	})

	c.Run("WalkParams", func(c *qt.C) {
		cfg := New()

		cfg.Set("x", maps.Params{})
		cfg.Set("y", maps.Params{})

		var got []string
		cfg.WalkParams(func(params ...maps.KeyParams) bool {
			got = append(got, params[len(params)-1].Key)
			return false
		})

		want := []string{"", "x", "y"}
		slices.Sort(got)
		slices.Sort(want)

		c.Assert(got, qt.DeepEquals, want)

		cfg = New()
		cfg.WalkParams(func(params ...maps.KeyParams) bool {
			return true
		})

		got = []string{""}
		want = []string{""}
		c.Assert(got, qt.DeepEquals, want)
	})

	c.Run("SetDefaults", func(c *qt.C) {
		cfg := New()

		cfg.SetDefaults(maps.Params{
			"foo": "bar",
			"bar": "baz",
		})

		c.Assert(cfg.Get("foo"), qt.Equals, "bar")
		c.Assert(cfg.Get("bar"), qt.Equals, "baz")
	})
}

func BenchmarkDefaultConfigProvider(b *testing.B) {
	type cfger interface {
		Get(key string) any
		Set(key string, value any)
		IsSet(key string) bool
	}

	newMap := func() map[string]any {
		return map[string]any{
			"a": map[string]any{
				"b": map[string]any{
					"c": 32,
					"d": 43,
				},
			},
			"b": 62,
		}
	}

	runMethods := func(b *testing.B, cfg cfger) {
		m := newMap()
		cfg.Set("mymap", m)
		cfg.Set("num", 32)
		if !(cfg.IsSet("mymap") && cfg.IsSet("mymap.a") && cfg.IsSet("mymap.a.b") && cfg.IsSet("mymap.a.b.c")) {
			b.Fatal("IsSet failed")
		}

		if cfg.Get("num") != 32 {
			b.Fatal("Get failed")
		}

		if cfg.Get("mymap.a.b.c") != 32 {
			b.Fatal("Get failed")
		}
	}

	b.Run("Custom", func(b *testing.B) {
		cfg := New()
		for b.Loop() {
			runMethods(b, cfg)
		}
	})
}
