// Copyright 2021 iLogtail 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 containercenter

import (
	"context"
	"encoding/json"
	"errors"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/events"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

var hostFileContent1 = `
192.168.5.3	8be13ee0dd9e
127.0.0.1  	  localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`

var hostFileContent2 = `
# Kubernetes-managed hosts file.
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
fe00::0	ip6-mcastprefix
fe00::1	ip6-allnodes
fe00::2	ip6-allrouters
172.20.4.5	nginx-5fd7568b67-4sh8c
`

func resetContainerCenter() {
	containerCenterInstance = nil
	onceDocker = sync.Once{}

}

func TestGetIpByHost_1(t *testing.T) {
	hostFileName := "./tmp_TestGetIpByHost.txt"
	os.WriteFile(hostFileName, []byte(hostFileContent1), 0x777)
	ip := getIPByHosts(hostFileName, "8be13ee0dd9e")
	if ip != "192.168.5.3" {
		t.Errorf("GetIpByHosts = %v, want %v", ip, "192.168.5.3")
	}
	os.Remove(hostFileName)
}

func TestGetIpByHost_2(t *testing.T) {
	hostFileName := "./tmp_TestGetIpByHost.txt"
	os.WriteFile(hostFileName, []byte(hostFileContent2), 0x777)
	ip := getIPByHosts(hostFileName, "nginx-5fd7568b67-4sh8c")
	if ip != "172.20.4.5" {
		t.Errorf("GetIpByHosts = %v, want %v", ip, "172.20.4.5")
	}
	os.Remove(hostFileName)
}

func TestFormatContainerJSONPath(t *testing.T) {
	testContainer1 := `{
		"ID": "111",
		"Name": "container1",
		"Image": "image1",
		"LogPath": "///var/log/pods/prod_podTest_podUidTest/container1/0.log",
		"Labels": {
				"io.kubernetes.container.name": "container1",
				"io.kubernetes.pod.name": "podTest",
				"io.kubernetes.pod.namespace": "prod",
				"io.kubernetes.pod.uid": "podUidTest"
		},
		"LogType": "json-file",
		"Env": {
				"KUBERNETES_PORT": "tcp://192.168.0.1:443",
				"KUBERNETES_PORT_443_TCP": "tcp://192.168.0.1:443",
				"KUBERNETES_PORT_443_TCP_ADDR": "192.168.0.1",
				"KUBERNETES_PORT_443_TCP_PORT": "443",
				"KUBERNETES_PORT_443_TCP_PROTO": "tcp",
				"KUBERNETES_SERVICE_HOST": "10.244.197.20",
				"KUBERNETES_SERVICE_PORT": "6443",
				"KUBERNETES_SERVICE_PORT_HTTPS": "443"
		},
		"Mounts": [
			{
				"Source": "//home/admin/logs",
				"Destination" : "//home/admin/logs",
				"Driver" : "host"
			}
		],
		"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/XX/diff",
                "MergedDir": "/var/lib/docker/overlay2/XX/merged",
                "UpperDir": "/../var/lib/docker/overlay2/XX/diff",
                "WorkDir": "/var/lib/docker/overlay2/XX/work"
            },
            "Name": "overlay2"
        },
		"Created": "2023-06-01T10:09:32.336427588+08:00",
		"State": {
				"Pid": 1001,
				"Status": "running"
		}
}`
	container1 := types.ContainerJSON{}
	err := json.Unmarshal([]byte(testContainer1), &container1)
	require.NoError(t, err)
	formatContainerJSONPath(&container1)
	require.Equal(t, "/var/log/pods/prod_podTest_podUidTest/container1/0.log", container1.LogPath)
	require.Equal(t, "/home/admin/logs", container1.Mounts[0].Source)
	require.Equal(t, "/home/admin/logs", container1.Mounts[0].Destination)
	require.Equal(t, "/var/lib/docker/overlay2/XX/diff", container1.GraphDriver.Data["UpperDir"])

	testContainer2 := `{
		"ID": "111",
		"Name": "container1",
		"Image": "image1",
		"LogPath": "/../var/log/pods/prod_podTest_podUidTest/container1/0.log",
		"Labels": {
				"io.kubernetes.container.name": "container1",
				"io.kubernetes.pod.name": "podTest",
				"io.kubernetes.pod.namespace": "prod",
				"io.kubernetes.pod.uid": "podUidTest"
		},
		"LogType": "json-file"
}`
	container2 := types.ContainerJSON{}
	err = json.Unmarshal([]byte(testContainer2), &container2)
	require.NoError(t, err)
	formatContainerJSONPath(&container2)
	require.Equal(t, "/../var/log/pods/prod_podTest_podUidTest/container1/0.log", container2.LogPath)
}

func TestGetAllAcceptedInfoV2(t *testing.T) {
	resetContainerCenter()
	dc := getContainerCenterInstance()

	newContainer := func(id string) *DockerInfoDetail {
		return dc.CreateInfoDetail(types.ContainerJSON{
			ContainerJSONBase: &types.ContainerJSONBase{
				ID:    id,
				Name:  id,
				State: &types.ContainerState{},
			},
			Config: &container.Config{
				Env: make([]string, 0),
			},
		}, "", false)
	}

	fullList := make(map[string]bool)
	matchList := make(map[string]*DockerInfoDetail)

	// Init.
	{
		dc.updateContainers(map[string]*DockerInfoDetail{
			"c1": newContainer("c1"),
		})

		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			nil, nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 1)
		require.Equal(t, len(matchList), 1)
		require.True(t, fullList["c1"])
		require.True(t, matchList["c1"] != nil)
		require.Equal(t, newCount, 1)
		require.Equal(t, delCount, 0)
		require.Equal(t, len(matchAddedList), 0)
		require.Equal(t, len(matchDeletedList), 0)
	}

	// New container.
	{
		dc.updateContainer("c2", newContainer("c2"))

		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			nil, nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 2)
		require.Equal(t, len(matchList), 2)
		require.True(t, fullList["c1"])
		require.True(t, fullList["c2"])
		require.True(t, matchList["c1"] != nil)
		require.True(t, matchList["c2"] != nil)
		require.Equal(t, newCount, 1)
		require.Equal(t, delCount, 0)
		require.Equal(t, len(matchAddedList), 1)
		require.Equal(t, len(matchDeletedList), 0)
	}

	// Delete container.
	{
		delete(dc.containerMap, "c1")

		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			nil, nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 1)
		require.Equal(t, len(matchList), 1)
		require.True(t, fullList["c2"])
		require.True(t, matchList["c2"] != nil)
		require.Equal(t, newCount, 0)
		require.Equal(t, delCount, 1)
		require.Equal(t, len(matchAddedList), 0)
		require.Equal(t, len(matchDeletedList), 1)
	}

	// New and Delete container.
	{
		dc.updateContainers(map[string]*DockerInfoDetail{
			"c3": newContainer("c3"),
			"c4": newContainer("c4"),
		})
		delete(dc.containerMap, "c2")

		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			nil, nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 2)
		require.Equal(t, len(matchList), 2)
		require.True(t, fullList["c3"])
		require.True(t, fullList["c4"])
		require.True(t, matchList["c3"] != nil)
		require.True(t, matchList["c4"] != nil)
		require.Equal(t, newCount, 2)
		require.Equal(t, delCount, 1)
		require.Equal(t, len(matchAddedList), 2)
		require.Equal(t, len(matchDeletedList), 1)
	}

	// With unmatched filter.
	fullList = make(map[string]bool)
	matchList = make(map[string]*DockerInfoDetail)
	{
		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			map[string]string{
				"label": "label",
			},
			nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 2)
		require.Equal(t, len(matchList), 0)
		require.True(t, fullList["c3"])
		require.True(t, fullList["c4"])
		require.Equal(t, newCount, 0)
		require.Equal(t, delCount, 0)
		require.Equal(t, len(matchAddedList), 0)
		require.Equal(t, len(matchDeletedList), 0)
	}

	// Delete unmatched container.
	{
		delete(dc.containerMap, "c3")

		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
			fullList,
			matchList,
			map[string]string{
				"label": "label",
			},
			nil, nil, nil, nil, nil, nil, nil, nil)
		require.Equal(t, len(fullList), 1)
		require.Equal(t, len(matchList), 0)
		require.True(t, fullList["c4"])
		require.Equal(t, newCount, 0)
		require.Equal(t, delCount, 0)
		require.Equal(t, len(matchAddedList), 0)
		require.Equal(t, len(matchDeletedList), 0)
	}
}

func TestK8SInfo_IsMatch(t *testing.T) {
	type fields struct {
		Namespace       string
		Pod             string
		ContainerName   string
		Labels          map[string]string
		PausedContainer bool
	}
	type args struct {
		filter *K8SFilter
	}
	filter := func(ns, pod, container string, includeK8sLabels, excludeK8sLabels map[string]string) *K8SFilter {
		filter, _ := CreateK8SFilter(ns, pod, container, includeK8sLabels, excludeK8sLabels)
		return filter
	}
	tests := []struct {
		name   string
		fields fields
		args   args
		want   bool
	}{
		{
			name: "namespaceMatch",
			fields: fields{
				Namespace:     "ns",
				Pod:           "pod",
				ContainerName: "container",
				Labels: map[string]string{
					"a": "b",
				},
				PausedContainer: false,
			},
			args: args{
				filter: filter("^ns$", "", "", nil, nil),
			},
			want: true,
		},
		{
			name: "podMatch",
			fields: fields{
				Namespace:     "ns",
				Pod:           "pod",
				ContainerName: "container",
				Labels: map[string]string{
					"a": "b",
				},
				PausedContainer: false,
			},
			args: args{
				filter: filter("", "^pod$", "", nil, nil),
			},
			want: true,
		},
		{
			name: "containerMatch",
			fields: fields{
				Namespace:     "ns",
				Pod:           "pod",
				ContainerName: "container",
				Labels: map[string]string{
					"a": "b",
				},
				PausedContainer: false,
			},
			args: args{
				filter: filter("", "", "^container$", nil, nil),
			},
			want: true,
		},
		{
			name: "includeLabelMatch",
			fields: fields{
				Namespace:     "ns",
				Pod:           "pod",
				ContainerName: "container",
				Labels: map[string]string{
					"a": "b",
					"c": "d",
				},
				PausedContainer: false,
			},
			args: args{
				filter: filter("^ns$", "", "", map[string]string{
					"a": "b",
					"e": "f",
				}, nil),
			},
			want: true,
		},
		{
			name: "excludeLabelMatch",
			fields: fields{
				Namespace:     "ns",
				Pod:           "pod",
				ContainerName: "container",
				Labels: map[string]string{
					"a": "b",
					"c": "d",
				},
				PausedContainer: false,
			},
			args: args{
				filter: filter("^ns$", "", "", nil, map[string]string{
					"a": "b",
				}),
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			info := &K8SInfo{
				Namespace:       tt.fields.Namespace,
				Pod:             tt.fields.Pod,
				ContainerName:   tt.fields.ContainerName,
				Labels:          tt.fields.Labels,
				PausedContainer: tt.fields.PausedContainer,
			}
			assert.Equalf(t, tt.want, info.IsMatch(tt.args.filter), "IsMatch(%v)", tt.args.filter)
		})
	}
}

type DockerClientMock struct {
	mock.Mock
}

type ContainerHelperMock struct {
	mock.Mock
}

// Events 实现了 DockerClient 的 Events 方法
func (m *DockerClientMock) Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) {
	args := m.Called(ctx, options)
	return args.Get(0).(chan events.Message), args.Get(1).(chan error)
}

func (m *DockerClientMock) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
	args := m.Called(ctx, imageID)
	return args.Get(0).(types.ImageInspect), args.Get(1).([]byte), args.Error(2)
}

func (m *DockerClientMock) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
	args := m.Called(ctx, containerID)
	return args.Get(0).(types.ContainerJSON), args.Error(1)
}
func (m *DockerClientMock) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
	args := m.Called(ctx, options)
	return args.Get(0).([]types.Container), args.Error(1)
}

func (m *ContainerHelperMock) ContainerProcessAlive(pid int) bool {
	args := m.Called(pid)
	return args.Get(0).(bool)
}

func TestContainerCenterEvents(t *testing.T) {
	containerCenterInstance = &ContainerCenter{}
	containerCenterInstance.imageCache = make(map[string]string)
	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)

	mockClient := DockerClientMock{}
	containerCenterInstance.client = &mockClient

	containerHelper := ContainerHelperMock{}

	// 创建一个模拟的事件通道
	eventChan := make(chan events.Message, 1)
	errChan := make(chan error, 1)

	mockClient.On("Events", mock.Anything, mock.Anything).Return(eventChan, errChan)

	go containerCenterInstance.eventListener()

	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(false).Once()
	mockClient.On("ContainerInspect", mock.Anything, "event1").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "event1",
			Name:  "name1",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()

	eventChan <- events.Message{ID: "event1", Status: "rename"}

	time.Sleep(5 * time.Second)
	containerLen := len(containerCenterInstance.containerMap)
	assert.Equal(t, 1, containerLen)

	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true).Once()
	mockClient.On("ContainerInspect", mock.Anything, "event1").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "event1",
			Name:  "start",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()
	eventChan <- events.Message{ID: "event1", Status: "start"}

	time.Sleep(5 * time.Second)
	// 设置期望
	close(eventChan)

	containerLen = len(containerCenterInstance.containerMap)
	assert.Equal(t, 1, containerLen)
}

func TestContainerCenterFetchAll(t *testing.T) {
	containerCenterInstance = &ContainerCenter{}
	containerCenterInstance.imageCache = make(map[string]string)
	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)

	mockClient := DockerClientMock{}
	containerCenterInstance.client = &mockClient

	containerHelper := ContainerHelperMock{}

	mockContainerListResult := []types.Container{
		{ID: "id1"},
		{ID: "id2"},
		{ID: "id3"},
	}

	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true)

	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult, nil).Once()

	mockClient.On("ContainerInspect", mock.Anything, "id1").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id1",
			Name:  "event_name1",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()
	mockClient.On("ContainerInspect", mock.Anything, "id2").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id2",
			Name:  "event_name2",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()
	// one failed inspect
	mockClient.On("ContainerInspect", mock.Anything, "id3").Return(types.ContainerJSON{}, errors.New("id3 not exist")).Times(3)

	err := containerCenterInstance.fetchAll()
	assert.Nil(t, err)

	containerLen := len(containerCenterInstance.containerMap)
	assert.Equal(t, 2, containerLen)

	mockContainerListResult2 := []types.Container{
		{ID: "id4"},
		{ID: "id5"},
	}

	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult2, nil).Once()

	mockClient.On("ContainerInspect", mock.Anything, "id4").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id4",
			Name:  "event_name4",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()

	mockClient.On("ContainerInspect", mock.Anything, "id5").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id5",
			Name:  "event_name5",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil).Once()

	err = containerCenterInstance.fetchAll()
	assert.Nil(t, err)

	containerLen = len(containerCenterInstance.containerMap)
	assert.Equal(t, 4, containerLen)
}

func TestContainerCenterFetchAllAndOne(t *testing.T) {
	containerCenterInstance = &ContainerCenter{}
	containerCenterInstance.imageCache = make(map[string]string)
	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)

	mockClient := DockerClientMock{}
	containerCenterInstance.client = &mockClient

	containerHelper := ContainerHelperMock{}

	mockContainerListResult := []types.Container{
		{ID: "id1"},
		{ID: "id2"},
	}

	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult, nil)

	mockClient.On("ContainerInspect", mock.Anything, "id1").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id1",
			Name:  "event_name1",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil)
	mockClient.On("ContainerInspect", mock.Anything, "id2").Return(types.ContainerJSON{
		ContainerJSONBase: &types.ContainerJSONBase{
			ID:    "id2",
			Name:  "event_name2",
			State: &types.ContainerState{},
		},
		Config: &container.Config{
			Env: make([]string, 0),
		},
	}, nil)

	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true).Times(2)

	err := containerCenterInstance.fetchAll()
	assert.Nil(t, err)

	containerCenterInstance.markRemove("id1")
	containerCenterInstance.markRemove("id2")

	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(false).Times(2)
	err = containerCenterInstance.fetchAll()
	assert.Nil(t, err)

	containerLen := len(containerCenterInstance.containerMap)
	assert.Equal(t, 2, containerLen)

	for _, container := range containerCenterInstance.containerMap {
		assert.Equal(t, true, container.deleteFlag)
	}
}

func TestInitClientMutiTime(t *testing.T) {
	containerCenterInstance = &ContainerCenter{}
	for i := 0; i < 100; i++ {
		err := containerCenterInstance.initClient()
		assert.NotNil(t, err)
		assert.Nil(t, containerCenterInstance.client)
	}
}

func TestFindAllEnvConfig(t *testing.T) {
	did := &DockerInfoDetail{
		ContainerInfo: types.ContainerJSON{
			Config: &container.Config{
				Env: []string{
					"loong_logs_key1=value1",
					"aliyun_logs_key1=value2",
					"loong_logs_key1_project=loong_project",
					"aliyun_logs_key1_logstore=aliyun_logstore",
					"xxxx_KEY=xxxx",
					"loong_logs_key1_tags=appName=ut1",
				},
			},
			ContainerJSONBase: &types.ContainerJSONBase{
				Name: "ut-container",
			},
		},
		ContainerNameTag: map[string]string{},
	}

	tests := []struct {
		envConfigPrefix    string
		selfConfigFlag     bool
		expectedConfigName string
		expectedResult     map[string]*EnvConfigInfo
	}{
		{
			envConfigPrefix:    "aliyun_logs_",
			selfConfigFlag:     false,
			expectedConfigName: "key1",
			expectedResult: map[string]*EnvConfigInfo{
				"key1":          {ConfigItemMap: map[string]string{"": "value1"}},
				"key1_project":  {ConfigItemMap: map[string]string{"project": "loong_project"}},
				"key1_logstore": {ConfigItemMap: map[string]string{"logstore": "aliyun_logstore"}},
			},
		},
		{
			envConfigPrefix: "aliyun_logs_",
			selfConfigFlag:  false,
			expectedResult:  map[string]*EnvConfigInfo{},
		},
	}

	for _, test := range tests {
		did.EnvConfigInfoMap = make(map[string]*EnvConfigInfo)
		if len(test.expectedResult) == 0 {
			did.ContainerInfo.Config.Env = append(did.ContainerInfo.Config.Env, "ALICLOUD_LOG_DOCKER_ENV_CONFIG_SELF=true")
		}
		did.FindAllEnvConfig(envConfigPrefix, test.selfConfigFlag)
		if len(test.expectedResult) == 0 {
			assert.Equal(t, len(did.EnvConfigInfoMap), 0)
		} else {
			config, exists := did.EnvConfigInfoMap[test.expectedConfigName]
			assert.True(t, exists, "Expected config for key %s not found", test.expectedConfigName)
			for key, expectedConfig := range test.expectedResult {
				for subKey, expectedValue := range expectedConfig.ConfigItemMap {
					value, ok := config.ConfigItemMap[subKey]
					assert.True(t, ok && value == expectedValue, "Expected config for key %s not found", key)
				}
			}
		}

		assert.Equal(t, len(did.ContainerNameTag), 1)
		assert.Equal(t, did.ContainerNameTag["appName"], "ut1")
	}
}

func TestEnvRegex(t *testing.T) {
	tests := []struct {
		name     string
		envKey   string
		expected bool
	}{
		// KUBERNETES_ 开头的变量应该匹配
		{"KUBERNETES_SERVICE_HOST", "KUBERNETES_SERVICE_HOST", true},
		{"KUBERNETES_SERVICE_PORT", "KUBERNETES_SERVICE_PORT", true},
		{"KUBERNETES_NODE_NAME", "KUBERNETES_NODE_NAME", true},
		{"KUBERNETES_POD_NAME", "KUBERNETES_POD_NAME", true},

		// 以 _SERVICE_HOST 结尾的变量应该匹配
		{"MYSQL_SERVICE_HOST", "MYSQL_SERVICE_HOST", true},
		{"REDIS_SERVICE_HOST", "REDIS_SERVICE_HOST", true},
		{"APP_SERVICE_HOST", "APP_SERVICE_HOST", true},
		{"YILI_SERVICE_1_SERVICE_HOST", "YILI_SERVICE_1_SERVICE_HOST", true},
		{"YILI_TEST_SERVICE_1000_SERVICE_HOST", "YILI_TEST_SERVICE_1000_SERVICE_HOST", true},
		{"YILI_TEST_SERVICE_1001_SERVICE_HOST", "YILI_TEST_SERVICE_1001_SERVICE_HOST", true},
		{"YILI_TEST_SERVICE_1002_SERVICE_HOST", "YILI_TEST_SERVICE_1002_SERVICE_HOST", true},

		// 以 _SERVICE_PORT 结尾的变量应该匹配
		{"MYSQL_SERVICE_PORT", "MYSQL_SERVICE_PORT", true},
		{"REDIS_SERVICE_PORT", "REDIS_SERVICE_PORT", true},
		{"APP_SERVICE_PORT", "APP_SERVICE_PORT", true},
		{"YILI_SERVICE_1_SERVICE_PORT", "YILI_SERVICE_1_SERVICE_PORT", true},
		{"YILI_TEST_SERVICE_1000_SERVICE_PORT", "YILI_TEST_SERVICE_1000_SERVICE_PORT", true},
		{"YILI_TEST_SERVICE_1001_SERVICE_PORT", "YILI_TEST_SERVICE_1001_SERVICE_PORT", true},
		{"YILI_TEST_SERVICE_1002_SERVICE_PORT", "YILI_TEST_SERVICE_1002_SERVICE_PORT", true},

		// 以 _SERVICE_PORT_PORT 结尾的变量应该匹配
		{"MYSQL_SERVICE_PORT_PORT", "MYSQL_SERVICE_PORT_PORT", true},
		{"REDIS_SERVICE_PORT_PORT", "REDIS_SERVICE_PORT_PORT", true},
		{"YILI_SERVICE_1_SERVICE_PORT_PORT", "YILI_SERVICE_1_SERVICE_PORT_PORT", true},
		{"YILI_TEST_SERVICE_1000_SERVICE_PORT_PORT", "YILI_TEST_SERVICE_1000_SERVICE_PORT_PORT", true},
		{"YILI_TEST_SERVICE_1001_SERVICE_PORT_PORT", "YILI_TEST_SERVICE_1001_SERVICE_PORT_PORT", true},
		{"YILI_TEST_SERVICE_1002_SERVICE_PORT_PORT", "YILI_TEST_SERVICE_1002_SERVICE_PORT_PORT", true},

		// 包含 _SERVICE_数字_PORT 的变量应该匹配
		{"MYSQL_SERVICE_3306_PORT", "MYSQL_SERVICE_3306_PORT", true},
		{"REDIS_SERVICE_6379_PORT", "REDIS_SERVICE_6379_PORT", true},
		{"APP_SERVICE_8080_PORT", "APP_SERVICE_8080_PORT", true},
		{"DB_SERVICE_5432_PORT_80", "DB_SERVICE_5432_PORT_80", true}, // 包含 PORT 后还有其他内容
		{"YILI_SERVICE_1_PORT", "YILI_SERVICE_1_PORT", true},
		{"YILI_SERVICE_1_PORT_8081_TCP", "YILI_SERVICE_1_PORT_8081_TCP", true},
		{"YILI_SERVICE_1_PORT_8081_TCP_ADDR", "YILI_SERVICE_1_PORT_8081_TCP_ADDR", true},
		{"YILI_SERVICE_1_PORT_8081_TCP_PORT", "YILI_SERVICE_1_PORT_8081_TCP_PORT", true},
		{"YILI_SERVICE_1_PORT_8081_TCP_PROTO", "YILI_SERVICE_1_PORT_8081_TCP_PROTO", true},
		{"YILI_TEST_SERVICE_1000_PORT", "YILI_TEST_SERVICE_1000_PORT", true},
		{"YILI_TEST_SERVICE_1000_PORT_8081_TCP", "YILI_TEST_SERVICE_1000_PORT_8081_TCP", true},
		{"YILI_TEST_SERVICE_1000_PORT_8081_TCP_ADDR", "YILI_TEST_SERVICE_1000_PORT_8081_TCP_ADDR", true},
		{"YILI_TEST_SERVICE_1000_PORT_8081_TCP_PORT", "YILI_TEST_SERVICE_1000_PORT_8081_TCP_PORT", true},
		{"YILI_TEST_SERVICE_1000_PORT_8081_TCP_PROTO", "YILI_TEST_SERVICE_1000_PORT_8081_TCP_PROTO", true},
		{"YILI_TEST_SERVICE_1001_PORT", "YILI_TEST_SERVICE_1001_PORT", true},
		{"YILI_TEST_SERVICE_1001_PORT_8081_TCP", "YILI_TEST_SERVICE_1001_PORT_8081_TCP", true},
		{"YILI_TEST_SERVICE_1001_PORT_8081_TCP_ADDR", "YILI_TEST_SERVICE_1001_PORT_8081_TCP_ADDR", true},
		{"YILI_TEST_SERVICE_1001_PORT_8081_TCP_PORT", "YILI_TEST_SERVICE_1001_PORT_8081_TCP_PORT", true},
		{"YILI_TEST_SERVICE_1001_PORT_8081_TCP_PROTO", "YILI_TEST_SERVICE_1001_PORT_8081_TCP_PROTO", true},
		{"YILI_TEST_SERVICE_1002_PORT", "YILI_TEST_SERVICE_1002_PORT", true},
		{"YILI_TEST_SERVICE_1002_PORT_8081_TCP", "YILI_TEST_SERVICE_1002_PORT_8081_TCP", true},
		{"YILI_TEST_SERVICE_1002_PORT_8081_TCP_ADDR", "YILI_TEST_SERVICE_1002_PORT_8081_TCP_ADDR", true},
		{"YILI_TEST_SERVICE_1002_PORT_8081_TCP_PORT", "YILI_TEST_SERVICE_1002_PORT_8081_TCP_PORT", true},
		{"YILI_TEST_SERVICE_1002_PORT_8081_TCP_PROTO", "YILI_TEST_SERVICE_1002_PORT_8081_TCP_PROTO", true},

		// 不应该匹配的变量
		{"PATH", "PATH", false},
		{"HOME", "HOME", false},
		{"USER", "USER", false},
		{"DATABASE_URL", "DATABASE_URL", false},
		{"APP_CONFIG", "APP_CONFIG", false},
		{"SERVICE_NAME", "SERVICE_NAME", false},               // 不以 _SERVICE_HOST 等结尾
		{"MY_KUBERNETES_VAR", "MY_KUBERNETES_VAR", false},     // 不以 KUBERNETES_ 开头
		{"SERVICE_HOST_BACKUP", "SERVICE_HOST_BACKUP", false}, // 不以 _SERVICE_HOST 结尾
		{"SERVICE_123_VAR", "SERVICE_123_VAR", false},         // 不是 SERVICE_数字_PORT 格式
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := envRegex.MatchString(tt.envKey)
			if result != tt.expected {
				t.Errorf("envRegex.MatchString(%q) = %v, expected %v", tt.envKey, result, tt.expected)
			}
		})
	}
}
