/*
Copyright (C) 2022-2025 ApeCloud Co., Ltd

This file is part of KubeBlocks project

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package core

import (
	"slices"
	"testing"

	parametersv1alpha1 "github.com/apecloud/kubeblocks/apis/parameters/v1alpha1"
	"github.com/apecloud/kubeblocks/test/testdata"
)

func TestCheckExcludeConfigDifference(t *testing.T) {
	type args struct {
		oldVersion map[string]string
		newVersion map[string]string
		keys       []string
	}
	tests := []struct {
		name string
		args args
		want bool
	}{{
		name: "emptyKeys",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
			},
			newVersion: map[string]string{
				"a": "test1",
			},
		},
		want: false,
	}, {
		name: "emptyKeys",
		args: args{
			oldVersion: map[string]string{
				"a": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
			},
		},
		want: true,
	}, {
		name: "emptyKeys",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
			},
			newVersion: map[string]string{
				"b": "test1",
			},
		},
		want: true,
	}, {
		name: "keyTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"b"},
		},
		want: false,
	}, {
		name: "keyTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"a"},
		},
		want: true,
	}, {
		name: "keyTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"a", "b"},
		},
		want: false,
	}, {
		name: "keyCountTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
				"c": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"b"},
		},
		want: true,
	}, {
		name: "notKeyTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
				"c": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"b", "e", "f", "g"},
		},
		want: true,
	}, {
		name: "notKeyTest",
		args: args{
			oldVersion: map[string]string{
				"a": "test1",
				"b": "test2",
				"c": "test2",
			},
			newVersion: map[string]string{
				"a": "test1",
				"b": "test3",
			},
			keys: []string{"b", "c", "f", "g"},
		},
		want: false,
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := checkExcludeConfigDifference(tt.args.oldVersion, tt.args.newVersion, tt.args.keys); got != tt.want {
				t.Errorf("checkExcludeConfigDifference() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCreateConfigurePatch(t *testing.T) {
	v1 := `
[mysqld]
key_buffer_size=16777216
max_connections=2666
authentication_policy=mysql_native_password,
back_log=5285
binlog_cache_size=32768
binlog_format=MIXED
binlog_order_commits=ON
binlog_row_image=FULL
connect_timeout=10
`
	v2 := `
[mysqld]
authentication_policy=mysql_native_password,
back_log=5285
binlog_cache_size=32768
binlog_format=MIXED
binlog_order_commits=ON
binlog_row_image=FULL
connect_timeout=10
key_buffer_size=18874368
max_connections=666
`

	type args struct {
		oldVersion        map[string]string
		newVersion        map[string]string
		format            parametersv1alpha1.CfgFileFormat
		keys              []string
		enableExcludeDiff bool
	}
	tests := []struct {
		name        string
		args        args
		want        *ConfigPatchInfo
		excludeDiff bool
		wantErr     bool
	}{{
		name: "patchTestWithoutKeys",
		args: args{
			oldVersion: map[string]string{
				"my.cnf": v1,
			},
			newVersion: map[string]string{
				"my.cnf": v2,
			},
			format:            parametersv1alpha1.Ini,
			enableExcludeDiff: true,
		},
		want:        &ConfigPatchInfo{IsModify: true},
		excludeDiff: false,
	}, {
		name: "failedPatchTestWithoutKeys",
		args: args{
			oldVersion: map[string]string{
				"my.cnf":    v1,
				"other.cnf": "context",
			},
			newVersion: map[string]string{
				"my.cnf":    v2,
				"other.cnf": "context",
			},
			format:            parametersv1alpha1.Ini,
			enableExcludeDiff: true,
		},
		want:        &ConfigPatchInfo{IsModify: true},
		excludeDiff: false,
		wantErr:     true,
	}, {
		name: "patchTest",
		args: args{
			oldVersion: map[string]string{
				"my.cnf":    v1,
				"other.cnf": "context",
			},
			newVersion: map[string]string{
				"my.cnf":    v2,
				"other.cnf": "context",
			},
			keys:              []string{"my.cnf"},
			format:            parametersv1alpha1.Ini,
			enableExcludeDiff: true,
		},
		want:        &ConfigPatchInfo{IsModify: true},
		excludeDiff: false,
	}, {
		name: "patchTest",
		args: args{
			oldVersion: map[string]string{
				"my.cnf":    v1,
				"other.cnf": "context",
			},
			newVersion: map[string]string{
				"my.cnf":    v1,
				"other.cnf": "context difference",
			},
			keys:              []string{"my.cnf"},
			format:            parametersv1alpha1.Ini,
			enableExcludeDiff: true,
		},
		want:        &ConfigPatchInfo{IsModify: false},
		excludeDiff: true,
	}, {
		name: "patchTest",
		args: args{
			oldVersion: map[string]string{
				"my.cnf":    v1,
				"other.cnf": "context",
			},
			newVersion: map[string]string{
				"my.cnf":    v2,
				"other.cnf": "context difference",
			},
			keys:              []string{"my.cnf"},
			format:            parametersv1alpha1.Ini,
			enableExcludeDiff: false,
		},
		want:        &ConfigPatchInfo{IsModify: true},
		excludeDiff: false,
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var configs []parametersv1alpha1.ComponentConfigDescription
			for k := range tt.args.oldVersion {
				if len(tt.args.keys) == 0 || slices.Contains(tt.args.keys, k) {
					configs = append(configs, parametersv1alpha1.ComponentConfigDescription{
						Name: k,
						FileFormatConfig: &parametersv1alpha1.FileFormatConfig{
							Format: tt.args.format,
						},
					})
				}
			}
			configRender := parametersv1alpha1.ParamConfigRendererSpec{Configs: configs}
			got, excludeDiff, err := CreateConfigPatch(tt.args.oldVersion, tt.args.newVersion, configRender, tt.args.enableExcludeDiff)
			if (err != nil) != tt.wantErr {
				t.Errorf("CreateConfigPatch() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !tt.wantErr && got.IsModify != tt.want.IsModify {
				t.Errorf("CreateConfigPatch() got = %v, want %v", got, tt.want)
			}
			if excludeDiff != tt.excludeDiff {
				t.Errorf("CreateConfigPatch() got1 = %v, want %v", excludeDiff, tt.excludeDiff)
			}
		})
	}
}

func TestLoadRawConfigObject(t *testing.T) {
	getFileContentFn := func(file string) string {
		content, _ := testdata.GetTestDataFileContent(file)
		return string(content)
	}

	type args struct {
		data         map[string]string
		formatConfig *parametersv1alpha1.FileFormatConfig
		keys         []string
	}
	tests := []struct {
		name    string
		args    args
		wantErr bool
	}{{
		name: "test",
		args: args{
			data: map[string]string{"key": getFileContentFn("cue_testdata/mysql.cnf")},
			formatConfig: &parametersv1alpha1.FileFormatConfig{
				Format: parametersv1alpha1.Ini,
				FormatterAction: parametersv1alpha1.FormatterAction{
					IniConfig: &parametersv1alpha1.IniConfig{
						SectionName: "mysqld",
					}},
			}},
		wantErr: false,
	}, {
		name: "test",
		args: args{
			data: map[string]string{"key": getFileContentFn("cue_testdata/pg14.conf")},
			formatConfig: &parametersv1alpha1.FileFormatConfig{
				Format: parametersv1alpha1.Properties,
			}},
		wantErr: false,
	}, {
		name: "test",
		args: args{
			data: map[string]string{
				"key":  getFileContentFn("cue_testdata/pg14.conf"),
				"key2": getFileContentFn("cue_testdata/mysql.cnf"),
			},
			keys: []string{"key"},
			formatConfig: &parametersv1alpha1.FileFormatConfig{
				Format: parametersv1alpha1.Properties,
			}},
		wantErr: false,
	}, {
		name: "test-failed",
		args: args{
			data: map[string]string{
				"key": getFileContentFn("cue_testdata/pg14.conf"),
			},
			keys: []string{"key"},
			formatConfig: &parametersv1alpha1.FileFormatConfig{
				Format: parametersv1alpha1.XML,
			}},
		wantErr: true,
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := LoadRawConfigObject(tt.args.data, tt.args.formatConfig, tt.args.keys)
			if (err != nil) != tt.wantErr {
				t.Errorf("LoadRawConfigObject() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
		})
	}
}

// func TestTransformConfigFileToKeyValueMap(t *testing.T) {
// 	mysqlConfig := `
// [mysqld]
// key_buffer_size=16777216
// log_error=/data/mysql/logs/mysql.log
// `
// 	mongodbConfig := `
// systemLog:
//   logRotate: reopen
//   path: /data/mongodb/logs/mongodb.log
//   verbosity: 0
// `
// 	tests := []struct {
// 		name         string
// 		fileName     string
// 		formatConfig *parametersv1alpha1.FileFormatConfig
// 		configData   []byte
// 		expected     map[string]string
// 	}{{
// 		name:     "mysql-test",
// 		fileName: "my.cnf",
// 		formatConfig: &parametersv1alpha1.FileFormatConfig{
// 			Format: parametersv1alpha1.Ini,
// 			FormatterAction: parametersv1alpha1.FormatterAction{
// 				IniConfig: &parametersv1alpha1.IniConfig{
// 					SectionName: "mysqld",
// 				},
// 			},
// 		},
// 		configData: []byte(mysqlConfig),
// 		expected: map[string]string{
// 			"key_buffer_size": "16777216",
// 			"log_error":       "/data/mysql/logs/mysql.log",
// 		},
// 	}, {
// 		name:     "mongodb-test",
// 		fileName: "mongodb.conf",
// 		formatConfig: &parametersv1alpha1.FileFormatConfig{
// 			Format: parametersv1alpha1.YAML,
// 			FormatterAction: parametersv1alpha1.FormatterAction{
// 				IniConfig: &parametersv1alpha1.IniConfig{
// 					SectionName: "default",
// 				},
// 			},
// 		},
// 		configData: []byte(mongodbConfig),
// 		expected: map[string]string{
// 			"systemLog.logRotate": "reopen",
// 			"systemLog.path":      "/data/mongodb/logs/mongodb.log",
// 			"systemLog.verbosity": "0",
// 		},
// 	}}
// 	for _, tt := range tests {
// 		t.Run(tt.name, func(t *testing.T) {
// 			res, _ := TransformConfigFileToKeyValueMap(tt.fileName, tt.formatConfig, tt.configData)
// 			if !reflect.DeepEqual(res, tt.expected) {
// 				t.Errorf("TransformConfigFileToKeyValueMap() res = %v, res %v", res, tt.expected)
// 				return
// 			}
// 		})
// 	}
// }
