// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"

	"github.com/BurntSushi/toml"
	. "github.com/pingcap/check"
	"github.com/pingcap/failpoint"
)

func (s *testConfigSuite) TestCloneConf(c *C) {
	c1, err := CloneConf(&defaultConf)
	c.Assert(err, IsNil)
	c2, err := CloneConf(c1)
	c.Assert(err, IsNil)
	c.Assert(reflect.DeepEqual(c1, c2), IsTrue)

	c1.Store = "abc"
	c1.Port = 2333
	c1.Log.EnableSlowLog = !c1.Log.EnableSlowLog
	c1.RepairTableList = append(c1.RepairTableList, "abc")
	c.Assert(c1.Store, Not(Equals), c2.Store)
	c.Assert(c1.Port, Not(Equals), c2.Port)
	c.Assert(c1.Log.EnableSlowLog, Not(Equals), c2.Log.EnableSlowLog)
	c.Assert(fmt.Sprintf("%v", c1.RepairTableList), Not(Equals), fmt.Sprintf("%v", c2.RepairTableList))
}

func (s *testConfigSuite) TestMergeConfigItems(c *C) {
	oriConf, _ := CloneConf(&defaultConf)
	oldConf, _ := CloneConf(oriConf)
	newConf, _ := CloneConf(oldConf)

	// allowed
	newConf.Performance.MaxProcs = 123
	newConf.Performance.MaxMemory = 123
	newConf.Performance.CrossJoin = false
	newConf.Performance.FeedbackProbability = 123
	newConf.Performance.QueryFeedbackLimit = 123
	newConf.Performance.PseudoEstimateRatio = 123
	newConf.OOMAction = "panic"
	newConf.MemQuotaQuery = 123
	newConf.TiKVClient.StoreLimit = 123

	// rejected
	newConf.Store = "tiflash"
	newConf.Port = 2333
	newConf.AdvertiseAddress = "1.2.3.4"
	newConf.Log.SlowThreshold = 2345

	as, rs := MergeConfigItems(oldConf, newConf)
	c.Assert(len(as), Equals, 10)
	c.Assert(len(rs), Equals, 3)
	for _, a := range as {
		_, ok := dynamicConfigItems[a]
		c.Assert(ok, IsTrue)
	}
	for _, a := range rs {
		_, ok := dynamicConfigItems[a]
		c.Assert(ok, IsFalse)
	}

	c.Assert(oldConf.Performance.MaxProcs, Equals, newConf.Performance.MaxProcs)
	c.Assert(oldConf.Performance.MaxMemory, Equals, newConf.Performance.MaxMemory)
	c.Assert(oldConf.Performance.CrossJoin, Equals, newConf.Performance.CrossJoin)
	c.Assert(oldConf.Performance.FeedbackProbability, Equals, newConf.Performance.FeedbackProbability)
	c.Assert(oldConf.Performance.QueryFeedbackLimit, Equals, newConf.Performance.QueryFeedbackLimit)
	c.Assert(oldConf.Performance.PseudoEstimateRatio, Equals, newConf.Performance.PseudoEstimateRatio)
	c.Assert(oldConf.OOMAction, Equals, newConf.OOMAction)
	c.Assert(oldConf.MemQuotaQuery, Equals, newConf.MemQuotaQuery)
	c.Assert(oldConf.TiKVClient.StoreLimit, Equals, newConf.TiKVClient.StoreLimit)
	c.Assert(oldConf.Log.SlowThreshold, Equals, newConf.Log.SlowThreshold)

	c.Assert(oldConf.Store, Equals, oriConf.Store)
	c.Assert(oldConf.Port, Equals, oriConf.Port)
	c.Assert(oldConf.AdvertiseAddress, Equals, oriConf.AdvertiseAddress)
}

func (s *testConfigSuite) TestAtomicWriteConfig(c *C) {
	conf, _ := CloneConf(&defaultConf)
	confPath := filepath.Join(os.TempDir(), "test-write-config.toml")
	conf.Performance.MaxMemory = 123
	conf.Performance.MaxProcs = 234
	conf.Performance.PseudoEstimateRatio = 3.45
	c.Assert(atomicWriteConfig(conf, confPath), IsNil)

	content, err := ioutil.ReadFile(confPath)
	c.Assert(err, IsNil)
	dconf, err := decodeConfig(string(content))
	c.Assert(err, IsNil)
	c.Assert(dconf.Performance.MaxMemory, Equals, uint64(123))
	c.Assert(dconf.Performance.MaxProcs, Equals, uint(234))
	c.Assert(dconf.Performance.PseudoEstimateRatio, Equals, 3.45)

	conf.Performance.MaxMemory = 321
	conf.Performance.MaxProcs = 432
	conf.Performance.PseudoEstimateRatio = 54.3
	c.Assert(atomicWriteConfig(conf, confPath), IsNil)

	content, err = ioutil.ReadFile(confPath)
	c.Assert(err, IsNil)
	dconf, err = decodeConfig(string(content))
	c.Assert(err, IsNil)
	c.Assert(dconf.Performance.MaxMemory, Equals, uint64(321))
	c.Assert(dconf.Performance.MaxProcs, Equals, uint(432))
	c.Assert(dconf.Performance.PseudoEstimateRatio, Equals, 54.3)
}

func (s *testConfigSuite) TestFlattenConfig(c *C) {
	toJSONStr := func(v interface{}) string {
		str, err := json.Marshal(v)
		c.Assert(err, IsNil)
		return string(str)
	}

	jsonConf := `{
	"k0": 233333,
	"k1": "v1",
	"k2": ["v2-1", "v2-2", "v2-3"],
	"k3": [{"k3-1":"v3-1"}, {"k3-2":"v3-2"}, {"k3-3":"v3-3"}],
	"k4": {
		"k4-1": [1, 2, 3, 4],
		"k4-2": [5, 6, 7, 8],
		"k4-3": [666]
	}}`
	nested := make(map[string]interface{})
	c.Assert(json.Unmarshal([]byte(jsonConf), &nested), IsNil)
	flatMap := FlattenConfigItems(nested)
	c.Assert(len(flatMap), Equals, 7)
	c.Assert(toJSONStr(flatMap["k0"]), Equals, "233333")
	c.Assert(flatMap["k1"], Equals, "v1")
	c.Assert(toJSONStr(flatMap["k2"]), Equals, `["v2-1","v2-2","v2-3"]`)
	c.Assert(toJSONStr(flatMap["k3"]), Equals, `[{"k3-1":"v3-1"},{"k3-2":"v3-2"},{"k3-3":"v3-3"}]`)
	c.Assert(toJSONStr(flatMap["k4.k4-1"]), Equals, `[1,2,3,4]`)
	c.Assert(toJSONStr(flatMap["k4.k4-2"]), Equals, `[5,6,7,8]`)
	c.Assert(toJSONStr(flatMap["k4.k4-3"]), Equals, `[666]`)

	tomlConf := `
port=4000
[log]
level='info'
format='text'
[isolation-read]
engines = ["tikv", "tiflash", "tidb"]
`
	nested = make(map[string]interface{})
	c.Assert(toml.Unmarshal([]byte(tomlConf), &nested), IsNil)
	flatMap = FlattenConfigItems(nested)
	c.Assert(len(flatMap), Equals, 4)
	c.Assert(toJSONStr(flatMap["port"]), Equals, "4000")
	c.Assert(toJSONStr(flatMap["log.level"]), Equals, `"info"`)
	c.Assert(toJSONStr(flatMap["log.format"]), Equals, `"text"`)
	c.Assert(toJSONStr(flatMap["isolation-read.engines"]), Equals, `["tikv","tiflash","tidb"]`)
}

func (s *testConfigSuite) TestTxnScopeValue(c *C) {
	failpoint.Enable("github.com/pingcap/tidb/config/injectTxnScope", `return("bj")`)
	isGlobal, v := GetTxnScopeFromConfig()
	c.Assert(isGlobal, IsFalse)
	c.Assert(v, Equals, "bj")
	failpoint.Disable("github.com/pingcap/tidb/config/injectTxnScope")
	failpoint.Enable("github.com/pingcap/tidb/config/injectTxnScope", `return("")`)
	isGlobal, v = GetTxnScopeFromConfig()
	c.Assert(isGlobal, IsTrue)
	c.Assert(v, Equals, "global")
	failpoint.Disable("github.com/pingcap/tidb/config/injectTxnScope")
	failpoint.Enable("github.com/pingcap/tidb/config/injectTxnScope", `return("global")`)
	isGlobal, v = GetTxnScopeFromConfig()
	c.Assert(isGlobal, IsFalse)
	c.Assert(v, Equals, "global")
	failpoint.Disable("github.com/pingcap/tidb/config/injectTxnScope")
}
