/*
 *
 * Copyright (c) 2025 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          <http://license.coscl.org.cn/MulanPSL2>
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 */

package k3s

import (
	"context"
	"fmt"
	"net"
	"os"
	"testing"
	"time"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	dockerapi "github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	specs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes"
	fake "k8s.io/client-go/kubernetes/fake"
	k8sioTesting "k8s.io/client-go/testing"

	"gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/docker"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/exec"
	"gopkg.openfuyao.cn/bkeadm/pkg/executor/k8s"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

const (
	testIPv4SegmentA = 127
	testIPv4SegmentB = 0
	testIPv4SegmentC = 0
	testIPv4SegmentD = 1

	testIPv4SegmentE = 192
	testIPv4SegmentF = 168
	testIPv4SegmentG = 1
	testIPv4SegmentH = 100

	testIPv4SegmentI = 172
	testIPv4SegmentJ = 17
	testIPv4SegmentK = 0
	testIPv4SegmentL = 2

	testIPv4SegmentM = 8
	testIPv4SegmentN = 8
	testIPv4SegmentO = 8
	testIPv4SegmentP = 8

	testPortValue = 6443
	testDirPath   = "/tmp/test_dir"
	testFilePath  = "/tmp/test_file"
	testFourValue = 4
)

var testLoopbackIP = net.IPv4(
	testIPv4SegmentA,
	testIPv4SegmentB,
	testIPv4SegmentC,
	testIPv4SegmentD,
).String()

var testHostIP = net.IPv4(
	testIPv4SegmentE,
	testIPv4SegmentF,
	testIPv4SegmentG,
	testIPv4SegmentH,
).String()

var testContainerIP = net.IPv4(
	testIPv4SegmentI,
	testIPv4SegmentJ,
	testIPv4SegmentK,
	testIPv4SegmentL,
).String()

var testDNSIP = net.IPv4(
	testIPv4SegmentM,
	testIPv4SegmentN,
	testIPv4SegmentO,
	testIPv4SegmentP,
).String()

var testPort = fmt.Sprintf("%d", testPortValue)

func TestEnsureDirExists(t *testing.T) {
	tests := []struct {
		name        string
		dir         string
		mockExists  func(string) bool
		mockMkdir   func(string, os.FileMode) error
		expectedErr bool
	}{
		{
			name: "directory exists",
			dir:  testDirPath,
			mockExists: func(path string) bool {
				return true
			},
			mockMkdir: func(path string, perm os.FileMode) error {
				return nil
			},
			expectedErr: false,
		},
		{
			name: "directory does not exist, create success",
			dir:  testDirPath,
			mockExists: func(path string) bool {
				return false
			},
			mockMkdir: func(path string, perm os.FileMode) error {
				return nil
			},
			expectedErr: false,
		},
		{
			name: "directory does not exist, create fails",
			dir:  testDirPath,
			mockExists: func(path string) bool {
				return false
			},
			mockMkdir: func(path string, perm os.FileMode) error {
				return fmt.Errorf("mkdir failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(utils.Exists, tt.mockExists)

			if tt.expectedErr {
				patches.ApplyFunc(os.MkdirAll, tt.mockMkdir)
			} else {
				patches.ApplyFunc(os.MkdirAll, tt.mockMkdir)
			}

			err := EnsureDirExists(tt.dir)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestIsKubernetesAvailable(t *testing.T) {
	tests := []struct {
		name           string
		setupMock      func() (*k8s.MockK8sClient, *gomonkey.Patches)
		mockNewClient  func(string) (k8s.KubernetesClient, error)
		expectedResult bool
	}{
		{
			name: "kubernetes available",
			setupMock: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Status.Conditions = []corev1.NodeCondition{
					{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
				}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient, patches
			},
			mockNewClient: func(kubeConfig string) (k8s.KubernetesClient, error) {
				return &k8s.MockK8sClient{}, nil
			},
			expectedResult: true,
		},
		{
			name: "kubernetes not available - client creation fails",
			setupMock: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				return &k8s.MockK8sClient{}, nil
			},
			mockNewClient: func(kubeConfig string) (k8s.KubernetesClient, error) {
				return nil, fmt.Errorf("client creation failed")
			},
			expectedResult: false,
		},
		{
			name: "kubernetes not available - no ready nodes",
			setupMock: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Status.Conditions = []corev1.NodeCondition{
					{Type: corev1.NodeReady, Status: corev1.ConditionFalse},
				}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient, patches
			},
			mockNewClient: func(kubeConfig string) (k8s.KubernetesClient, error) {
				return &k8s.MockK8sClient{}, nil
			},
			expectedResult: false,
		},
		{
			name: "kubernetes not available - no nodes",
			setupMock: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				fakeClient := fake.NewSimpleClientset()
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient, patches
			},
			mockNewClient: func(kubeConfig string) (k8s.KubernetesClient, error) {
				return &k8s.MockK8sClient{}, nil
			},
			expectedResult: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockClient, setupPatches := tt.setupMock()
			if setupPatches != nil {
				defer setupPatches.Reset()
			}

			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(k8s.NewKubernetesClient, func(kubeConfig string) (k8s.KubernetesClient, error) {
				client, err := tt.mockNewClient(kubeConfig)
				if err != nil {
					return nil, err
				}
				if setupPatches != nil && mockClient != nil {
					return mockClient, nil
				}
				return client, nil
			})

			originalK8s := global.K8s
			defer func() { global.K8s = originalK8s }()

			result := isKubernetesAvailable()

			assert.Equal(t, tt.expectedResult, result)
		})
	}
}

func TestStartK3sWithContainerd(t *testing.T) {

	tests := []struct {
		name                string
		cfg                 Config
		localImage          string
		mockIsAvailable     func() bool
		mockPrepareImages   func(string, string, string, string, string)
		mockEnsureImage     func(string) error
		mockContainerRemove func(string) error
		mockExists          func(string) bool
		mockMkdir           func(string, os.FileMode) error
		mockCustomCA        func() error
		mockGetImageRepoIP  func(string, string, string, string) (string, string)
		mockGenerateConfig  func(string, string) error
		mockLogFormat       func(string, ...any)
		mockRun             func([]string) error
		mockSleep           func(time.Duration)
		mockCP              func(string, string) error
		mockSetupKubeconfig func(string, string) error
		expectedErr         bool
	}{
		{
			name: "kubernetes already available",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockIsAvailable: func() bool { return true },
			expectedErr:     false,
		},
		{
			name: "start k3s success",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:          "",
			mockIsAvailable:     func() bool { return false },
			mockPrepareImages:   func(string, string, string, string, string) {},
			mockEnsureImage:     func(string) error { return nil },
			mockContainerRemove: func(string) error { return nil },
			mockExists:          func(string) bool { return true },
			mockMkdir:           func(string, os.FileMode) error { return nil },
			mockCustomCA:        func() error { return nil },
			mockGetImageRepoIP:  func(string, string, string, string) (string, string) { return "test.repo:443", testLoopbackIP },
			mockGenerateConfig:  func(string, string) error { return nil },
			mockLogFormat:       func(string, ...any) {},
			mockRun:             func([]string) error { return nil },
			mockSleep:           func(time.Duration) {},
			mockCP:              func(string, string) error { return nil },
			mockSetupKubeconfig: func(string, string) error { return nil },
			expectedErr:         false,
		},
		{
			name: "ensure image fails",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:          "",
			mockIsAvailable:     func() bool { return false },
			mockPrepareImages:   func(string, string, string, string, string) {},
			mockEnsureImage:     func(string) error { return fmt.Errorf("ensure image failed") },
			mockContainerRemove: func(string) error { return nil },
			expectedErr:         true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(isKubernetesAvailable, tt.mockIsAvailable)

			if tt.mockPrepareImages != nil {
				patches.ApplyFunc(prepareK3sImages, tt.mockPrepareImages)
			}
			if tt.mockEnsureImage != nil {
				patches.ApplyFunc(containerd.EnsureImageExists, func(string) error {
					return nil
				})
			}
			if tt.mockContainerRemove != nil {
				patches.ApplyFunc(containerd.ContainerRemove, tt.mockContainerRemove)
			}
			if tt.mockExists != nil {
				patches.ApplyFunc(utils.Exists, tt.mockExists)
			}
			if tt.mockMkdir != nil {
				patches.ApplyFunc(os.MkdirAll, tt.mockMkdir)
			}
			if tt.mockCustomCA != nil {
				patches.ApplyFunc(customCA, tt.mockCustomCA)
			}
			if tt.mockGetImageRepoIP != nil {
				patches.ApplyFunc(getImageRepoIP, tt.mockGetImageRepoIP)
			}
			if tt.mockGenerateConfig != nil {
				patches.ApplyFunc(generateRegistriesConfig, tt.mockGenerateConfig)
			}
			if tt.mockLogFormat != nil {
				patches.ApplyFunc(log.SteppedInfo, tt.mockLogFormat)
			}
			if tt.mockRun != nil {
				patches.ApplyFunc(containerd.Run, tt.mockRun)
			}
			if tt.mockSleep != nil {
				patches.ApplyFunc(time.Sleep, tt.mockSleep)
			}
			if tt.mockCP != nil {
				patches.ApplyFunc(containerd.CP, tt.mockCP)
			}
			if tt.mockSetupKubeconfig != nil {
				patches.ApplyFunc(setupKubeconfigAndWaitCluster, tt.mockSetupKubeconfig)
			}

			err := StartK3sWithContainerd(tt.cfg, tt.localImage)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestCustomCA(t *testing.T) {

	tests := []struct {
		name          string
		mockExists    func(string) bool
		mockMkdir     func(string, os.FileMode) error
		mockWriteFile func(string, []byte, os.FileMode) error
		mockExecute   func(*exec.CommandExecutor, string, ...string) (string, error)
		expectedErr   bool
	}{
		{
			name: "custom CA success",
			mockExists: func(string) bool {
				return false
			},
			mockMkdir: func(string, os.FileMode) error {
				return nil
			},
			mockWriteFile: func(string, []byte, os.FileMode) error {
				return nil
			},
			mockExecute: func(*exec.CommandExecutor, string, ...string) (string, error) {
				return "", nil
			},
			expectedErr: false,
		},
		{
			name: "mkdir fails",
			mockExists: func(string) bool {
				return false
			},
			mockMkdir: func(string, os.FileMode) error {
				return fmt.Errorf("mkdir failed")
			},
			expectedErr: true,
		},
		{
			name: "write file fails",
			mockExists: func(string) bool {
				return false
			},
			mockMkdir: func(string, os.FileMode) error {
				return nil
			},
			mockWriteFile: func(string, []byte, os.FileMode) error {
				return fmt.Errorf("write file failed")
			},
			expectedErr: true,
		},
		{
			name: "execute fails",
			mockExists: func(string) bool {
				return false
			},
			mockMkdir: func(string, os.FileMode) error {
				return nil
			},
			mockWriteFile: func(string, []byte, os.FileMode) error {
				return nil
			},
			mockExecute: func(*exec.CommandExecutor, string, ...string) (string, error) {
				return "execution failed", fmt.Errorf("execution failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(utils.Exists, tt.mockExists)

			patches.ApplyFunc(os.MkdirAll, tt.mockMkdir)
			patches.ApplyFunc(os.WriteFile, tt.mockWriteFile)

			mockExecutor := &exec.CommandExecutor{}
			patches.ApplyMethod(mockExecutor, "ExecuteCommandWithCombinedOutput", tt.mockExecute)

			err := customCA()

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestFixCoreDnsLoop(t *testing.T) {

	tests := []struct {
		name             string
		otherRepo        string
		imageRepo        string
		mockCheckConfig  func() error
		mockVerifyConfig func() error
		mockVerifyRun    func() error
		mockModImage     func(string, string) error
		mockExecute      func(*exec.CommandExecutor, string, ...string) (string, error)
		expectedErr      bool
	}{
		{
			name:      "fix coreDNS loop success",
			otherRepo: "test.repo",
			imageRepo: "registry.test.com",
			mockCheckConfig: func() error {
				return nil
			},
			mockVerifyConfig: func() error {
				return nil
			},
			mockVerifyRun: func() error {
				return nil
			},
			mockModImage: func(string, string) error {
				return nil
			},
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				return "", nil
			},
			expectedErr: false,
		},
		{
			name:      "check config fails",
			otherRepo: "test.repo",
			imageRepo: "registry.test.com",
			mockCheckConfig: func() error {
				return fmt.Errorf("check config failed")
			},
			expectedErr: true,
		},
		{
			name:      "verify config fails",
			otherRepo: "test.repo",
			imageRepo: "registry.test.com",
			mockCheckConfig: func() error {
				return nil
			},
			mockVerifyConfig: func() error {
				return fmt.Errorf("verify config failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(checkCurrentCoreDNSConfig, tt.mockCheckConfig)

			if tt.mockVerifyConfig != nil {
				patches.ApplyFunc(verifyCoreDNSConfig, tt.mockVerifyConfig)
			}
			if tt.mockVerifyRun != nil {
				patches.ApplyFunc(verifyCoreDNSRunning, tt.mockVerifyRun)
			}
			if tt.mockModImage != nil {
				patches.ApplyFunc(ModK3sCorednsImage, tt.mockModImage)
			}
			if tt.mockExecute != nil {
				mockExecutor := &exec.CommandExecutor{}
				patches.ApplyMethod(mockExecutor, "ExecuteCommandWithCombinedOutput", tt.mockExecute)
			}

			err := FixCoreDnsLoop(tt.otherRepo, tt.imageRepo)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestModCorednsConfigWithRetry(t *testing.T) {

	tests := []struct {
		name        string
		otherRepo   string
		imageRepo   string
		mockFixLoop func(string, string) error
		expectedErr bool
	}{
		{
			name:      "mod config success on first try",
			otherRepo: "test.repo",
			imageRepo: "registry.test.com",
			mockFixLoop: func(string, string) error {
				return nil
			},
			expectedErr: false,
		},
		{
			name:      "mod config fails after retries",
			otherRepo: "test.repo",
			imageRepo: "registry.test.com",
			mockFixLoop: func(string, string) error {
				return fmt.Errorf("fix loop failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(FixCoreDnsLoop, tt.mockFixLoop)

			patches.ApplyFunc(time.Sleep, func(time.Duration) {})
			patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})

			err := ModCorednsConfigWithRetry(tt.otherRepo, tt.imageRepo)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestModK3sCorednsImage(t *testing.T) {

	tests := []struct {
		name          string
		otherRepo     string
		imageRepo     string
		mockSleep     func(time.Duration)
		mockExecute   func(*exec.CommandExecutor, string, ...string) (string, error)
		mockLogFormat func(string, ...any)
		expectedErr   bool
	}{
		{
			name:      "mod coredns image success",
			otherRepo: "",
			imageRepo: "registry.test.com",
			mockSleep: func(time.Duration) {},
			mockExecute: func(*exec.CommandExecutor, string, ...string) (string, error) {
				return "True", nil
			},
			mockLogFormat: func(string, ...any) {},
			expectedErr:   false,
		},
		{
			name:      "execute command fails",
			otherRepo: "registry.test.com",
			imageRepo: "registry.test.com",
			mockSleep: func(time.Duration) {},
			mockExecute: func(*exec.CommandExecutor, string, ...string) (string, error) {
				return "True", fmt.Errorf("execution failed")
			},

			mockLogFormat: func(string, ...any) {},
			expectedErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(time.Sleep, tt.mockSleep)

			patches.ApplyFunc(log.SteppedInfo, tt.mockLogFormat)
			patches.ApplyFunc((*exec.CommandExecutor).ExecuteCommandWithCombinedOutput, tt.mockExecute)

			err := ModK3sCorednsImage(tt.otherRepo, tt.imageRepo)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestStartK3sWithDocker(t *testing.T) {
	tests := []struct {
		name                  string
		cfg                   Config
		localImage            string
		mockIsAvailable       func() bool
		mockPrepareEnv        func(Config, string) (string, string, error)
		mockLogFormat         func(string, ...any)
		mockBuildConfig       func(string) *container.Config
		mockBuildHostConfig   func(string, string, string) *container.HostConfig
		mockDockerRun         func(c *docker.Client, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) error
		mockSleep             func(time.Duration)
		mockCopyFromContainer func(c *docker.Client, containerId, srcPath, dstPath string) error
		mockSetupKubeconfig   func(string, string) error
		expectedErr           bool
	}{
		{
			name: "kubernetes already available",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockIsAvailable: func() bool { return true },
			expectedErr:     false,
		},
		{
			name: "prepare environment fails",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockIsAvailable: func() bool { return false },
			mockPrepareEnv: func(Config, string) (string, string, error) {
				return "", "", fmt.Errorf("prepare env failed")
			},
			expectedErr: true,
		},
		{
			name: "start k3s with docker success",
			cfg: Config{
				OtherRepo:      "test.repo",
				OtherRepoIP:    testLoopbackIP,
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockIsAvailable: func() bool { return false },
			mockPrepareEnv: func(Config, string) (string, string, error) {
				return "test.repo:443", testLoopbackIP, nil
			},
			mockLogFormat: func(string, ...any) {},
			mockBuildConfig: func(string) *container.Config {
				return &container.Config{}
			},
			mockBuildHostConfig: func(string, string, string) *container.HostConfig {
				return &container.HostConfig{}
			},
			mockDockerRun: func(c *docker.Client, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) error {
				return nil
			},
			mockSleep: func(time.Duration) {},
			mockCopyFromContainer: func(c *docker.Client, containerId, srcPath, dstPath string) error {
				return nil
			},
			mockSetupKubeconfig: func(string, string) error {
				return nil
			},
			expectedErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(isKubernetesAvailable, tt.mockIsAvailable)

			if tt.mockPrepareEnv != nil {
				patches.ApplyFunc(prepareK3sEnvironment, tt.mockPrepareEnv)
			}

			if tt.mockLogFormat != nil {
				patches.ApplyFunc(log.SteppedInfo, tt.mockLogFormat)
			}

			if tt.mockBuildConfig != nil {
				patches.ApplyFunc(buildK3sContainerConfig, tt.mockBuildConfig)
			}

			if tt.mockBuildHostConfig != nil {
				patches.ApplyFunc(buildK3sHostConfig, tt.mockBuildHostConfig)
			}

			if tt.mockDockerRun != nil {
				patches.ApplyFunc((*docker.Client).Run, tt.mockDockerRun)
			}

			if tt.mockSleep != nil {
				patches.ApplyFunc(time.Sleep, tt.mockSleep)
			}

			if tt.mockCopyFromContainer != nil {
				patches.ApplyFunc((*docker.Client).CopyFromContainer, tt.mockCopyFromContainer)
			}

			if tt.mockSetupKubeconfig != nil {
				patches.ApplyFunc(setupKubeconfigAndWaitCluster, tt.mockSetupKubeconfig)
			}

			if global.Docker == nil {
				global.Docker = &docker.Client{}
			}
			err := StartK3sWithDocker(tt.cfg, tt.localImage)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestReadKubeconfig(t *testing.T) {

	tests := []struct {
		name           string
		mockReadFile   func(string) ([]byte, error)
		expectedErr    bool
		expectedResult bool
	}{
		{
			name: "read kubeconfig success",
			mockReadFile: func(path string) ([]byte, error) {
				return []byte("test-kubeconfig-content"), nil
			},
			expectedErr:    false,
			expectedResult: true,
		},
		{
			name: "read kubeconfig fails",
			mockReadFile: func(path string) ([]byte, error) {
				return nil, fmt.Errorf("file not found")
			},
			expectedErr:    true,
			expectedResult: false,
		},
		{
			name: "read kubeconfig returns empty",
			mockReadFile: func(path string) ([]byte, error) {
				return []byte{}, nil
			},
			expectedErr:    true,
			expectedResult: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(os.ReadFile, tt.mockReadFile)

			patches.ApplyFunc(time.Sleep, func(time.Duration) {})

			result, err := readKubeconfig()

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, result)
			}
		})
	}
}

func TestProcessKubeconfig(t *testing.T) {
	tests := []struct {
		name           string
		hostIP         string
		kubernetesPort string
		kubeconfigData []byte
		mockUserHome   func() (string, error)
		mockMkdirAll   func(string, os.FileMode) error
		mockWriteFile  func(string, []byte, os.FileMode) error
		mockRemove     func(string) error
		expectedErr    bool
	}{
		{
			name:           "process kubeconfig success",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			kubeconfigData: []byte("apiVersion: v1\nclusters:\n- cluster:\n    server: https://127.0.0.1:36443\n  name: default\ncontexts:\n- context:\n    cluster: default\n    user: default\n  name: default\ncurrent-context: default\nkind: Config\npreferences: {}\nusers:\n- name: default\n  user:\n    token: test-token"),
			mockUserHome:   func() (string, error) { return "/home/testuser", nil },
			mockMkdirAll:   func(string, os.FileMode) error { return nil },
			mockWriteFile:  func(string, []byte, os.FileMode) error { return nil },
			mockRemove:     func(string) error { return nil },
			expectedErr:    false,
		},
		{
			name:           "get user home fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			kubeconfigData: []byte("test-content"),
			mockUserHome:   func() (string, error) { return "", fmt.Errorf("get home failed") },
			expectedErr:    true,
		},
		{
			name:           "mkdir fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			kubeconfigData: []byte("test-content"),
			mockUserHome:   func() (string, error) { return "/home/testuser", nil },
			mockMkdirAll:   func(string, os.FileMode) error { return fmt.Errorf("mkdir failed") },
			expectedErr:    true,
		},
		{
			name:           "write kubeconfig fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			kubeconfigData: []byte("test-content"),
			mockUserHome:   func() (string, error) { return "/home/testuser", nil },
			mockMkdirAll:   func(string, os.FileMode) error { return nil },
			mockWriteFile:  func(string, []byte, os.FileMode) error { return fmt.Errorf("write failed") },
			expectedErr:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(os.UserHomeDir, tt.mockUserHome)

			patches.ApplyFunc(os.MkdirAll, tt.mockMkdirAll)
			patches.ApplyFunc(os.WriteFile, tt.mockWriteFile)
			patches.ApplyFunc(os.Remove, tt.mockRemove)
			patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})

			result, err := processKubeconfig(tt.hostIP, tt.kubernetesPort, tt.kubeconfigData)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Contains(t, result, tt.hostIP)
			}
		})
	}
}

func TestWaitForK8sClient(t *testing.T) {

	tests := []struct {
		name           string
		kubeconfigPath string
		mockNewClient  func(string) (k8s.KubernetesClient, error)
		expectedErr    bool
	}{
		{
			name:           "wait for k8s client success",
			kubeconfigPath: "/home/testuser/.kube/config",
			mockNewClient: func(path string) (k8s.KubernetesClient, error) {
				return &k8s.MockK8sClient{}, nil
			},
			expectedErr: false,
		},
		{
			name:           "wait for k8s client fails",
			kubeconfigPath: "/home/testuser/.kube/config",
			mockNewClient: func(path string) (k8s.KubernetesClient, error) {
				return nil, fmt.Errorf("client creation failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(k8s.NewKubernetesClient, tt.mockNewClient)

			patches.ApplyFunc(time.Sleep, func(time.Duration) {})

			originalK8s := global.K8s
			global.K8s = nil
			defer func() { global.K8s = originalK8s }()

			err := waitForK8sClient(tt.kubeconfigPath)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestWaitForNodeReady(t *testing.T) {

	tests := []struct {
		name        string
		mockGetNode func(*k8s.MockK8sClient, string) error
		expectedErr bool
	}{
		{
			name: "node ready success",
			mockGetNode: func(client *k8s.MockK8sClient, name string) error {
				return nil
			},
			expectedErr: false,
		},
		{
			name: "node not ready",
			mockGetNode: func(client *k8s.MockK8sClient, name string) error {
				return fmt.Errorf("node not found")
			},
			expectedErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalK8s := global.K8s
			mockClient := &k8s.MockK8sClient{}
			global.K8s = mockClient
			defer func() { global.K8s = originalK8s }()

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc(time.Sleep, func(time.Duration) {})

			err := waitForNodeReady()

			assert.NoError(t, err)
			patches.Reset()
		})
	}
}

func TestCreateKubeconfigSecret(t *testing.T) {
	tests := []struct {
		name           string
		kubeconfigData string
		expectError    bool
	}{
		{
			name:           "create secret success",
			kubeconfigData: "test-kubeconfig-content",
			expectError:    false,
		},
		{
			name:           "create secret fails",
			kubeconfigData: "test-kubeconfig-content",
			expectError:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalK8s := global.K8s
			defer func() { global.K8s = originalK8s }()

			fakeClient := fake.NewSimpleClientset()

			if tt.expectError {
				fakeClient.PrependReactor("create", "secrets", func(action k8sioTesting.Action) (handled bool, ret k8sruntime.Object, err error) {
					return true, nil, fmt.Errorf("secret creation failed")
				})
			}

			mockClient := &k8s.MockK8sClient{}
			global.K8s = mockClient

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc((*k8s.MockK8sClient).GetClient, func(m *k8s.MockK8sClient) kubernetes.Interface {
				return fakeClient
			})

			err := createKubeconfigSecret(tt.kubeconfigData)

			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestSetupKubeconfigAndWaitCluster(t *testing.T) {

	tests := []struct {
		name               string
		hostIP             string
		kubernetesPort     string
		mockReadKubeconfig func() ([]byte, error)
		mockProcess        func(string, string, []byte) (string, error)
		mockUserHome       func() (string, error)
		mockWaitClient     func(string) error
		mockWaitNode       func() error
		mockCreateSecret   func(string) error
		expectedErr        bool
	}{
		{
			name:           "setup kubeconfig success",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			mockReadKubeconfig: func() ([]byte, error) {
				return []byte("test-kubeconfig"), nil
			},
			mockProcess: func(hostIP, kubernetesPort string, data []byte) (string, error) {
				return "processed-kubeconfig", nil
			},
			mockUserHome:     func() (string, error) { return "/home/testuser", nil },
			mockWaitClient:   func(string) error { return nil },
			mockWaitNode:     func() error { return nil },
			mockCreateSecret: func(string) error { return nil },
			expectedErr:      false,
		},
		{
			name:           "read kubeconfig fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			mockReadKubeconfig: func() ([]byte, error) {
				return nil, fmt.Errorf("read failed")
			},
			expectedErr: true,
		},
		{
			name:           "process kubeconfig fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			mockReadKubeconfig: func() ([]byte, error) {
				return []byte("test-kubeconfig"), nil
			},
			mockProcess: func(hostIP, kubernetesPort string, data []byte) (string, error) {
				return "", fmt.Errorf("process failed")
			},
			expectedErr: true,
		},
		{
			name:           "wait client fails",
			hostIP:         testLoopbackIP,
			kubernetesPort: testPort,
			mockReadKubeconfig: func() ([]byte, error) {
				return []byte("test-kubeconfig"), nil
			},
			mockProcess: func(hostIP, kubernetesPort string, data []byte) (string, error) {
				return "processed-kubeconfig", nil
			},
			mockUserHome:   func() (string, error) { return "/home/testuser", nil },
			mockWaitClient: func(string) error { return fmt.Errorf("wait client failed") },
			expectedErr:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(readKubeconfig, tt.mockReadKubeconfig)

			if tt.mockProcess != nil {
				patches.ApplyFunc(processKubeconfig, tt.mockProcess)
			}
			if tt.mockUserHome != nil {
				patches.ApplyFunc(os.UserHomeDir, tt.mockUserHome)
			}
			if tt.mockWaitClient != nil {
				patches.ApplyFunc(waitForK8sClient, tt.mockWaitClient)
			}
			if tt.mockWaitNode != nil {
				patches.ApplyFunc(waitForNodeReady, tt.mockWaitNode)
			}
			if tt.mockCreateSecret != nil {
				patches.ApplyFunc(createKubeconfigSecret, tt.mockCreateSecret)
			}
			patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})

			err := setupKubeconfigAndWaitCluster(tt.hostIP, tt.kubernetesPort)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestPrepareK3sImages(t *testing.T) {

	tests := []struct {
		name          string
		otherRepo     string
		imageRepo     string
		imageRepoPort string
		localImage    string
	}{
		{
			name:          "with other repo",
			otherRepo:     "test.repo",
			imageRepo:     "registry.test.com",
			imageRepoPort: "5000",
			localImage:    "",
		},
		{
			name:          "without other repo",
			otherRepo:     "",
			imageRepo:     "registry.test.com",
			imageRepoPort: "5000",
			localImage:    "",
		},
		{
			name:          "with local image",
			otherRepo:     "",
			imageRepo:     "registry.test.com",
			imageRepoPort: "5000",
			localImage:    "local-image",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			prepareK3sImages("", tt.otherRepo, tt.imageRepoPort, tt.imageRepo, tt.localImage)

			if tt.otherRepo != "" && tt.localImage == "" {
				assert.Contains(t, k3sImage, tt.otherRepo)
			} else {
				assert.Contains(t, k3sImage, tt.imageRepoPort)
			}
		})
	}
}

func TestGetImageRepoIP(t *testing.T) {

	tests := []struct {
		name           string
		otherRepo      string
		otherRepoIP    string
		hostIP         string
		imageRepo      string
		localImagePath string
		mockInspect    func(string) (containerd.NerdContainerInfo, error)
		expectedRepo   string
		expectedIP     string
	}{
		{
			name:           "with other repo containing image repo",
			otherRepo:      "registry.test.com:5000/test",
			otherRepoIP:    testHostIP,
			hostIP:         testLoopbackIP,
			imageRepo:      "registry.test.com",
			localImagePath: "",
			mockInspect: func(name string) (containerd.NerdContainerInfo, error) {
				return containerd.NerdContainerInfo{}, fmt.Errorf("container not found")
			},
			expectedRepo: "registry.test.com:5000",
			expectedIP:   testHostIP,
		},
		{
			name:           "with other repo not containing image repo",
			otherRepo:      "other.repo:5000/test",
			otherRepoIP:    testHostIP,
			hostIP:         testLoopbackIP,
			imageRepo:      "registry.test.com",
			localImagePath: "",
			mockInspect: func(name string) (containerd.NerdContainerInfo, error) {
				return containerd.NerdContainerInfo{}, fmt.Errorf("container not found")
			},
			expectedRepo: "registry.test.com:443",
			expectedIP:   testLoopbackIP,
		},
		{
			name:           "inspect succeeds",
			otherRepo:      "",
			otherRepoIP:    "",
			hostIP:         testLoopbackIP,
			imageRepo:      "registry.test.com",
			localImagePath: "",
			mockInspect: func(name string) (containerd.NerdContainerInfo, error) {
				return containerd.NerdContainerInfo{Id: "test-container", NetworkSettings: struct {
					IPAddress  string `json:"IPAddress"`
					MacAddress string `json:"MacAddress"`
				}{IPAddress: testContainerIP}}, nil
			},
			expectedRepo: "registry.test.com:443",
			expectedIP:   testContainerIP,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(containerd.ContainerInspect, tt.mockInspect)

			repo, ip := getImageRepoIP(tt.otherRepo, tt.otherRepoIP, tt.hostIP, tt.imageRepo, tt.localImagePath)

			assert.Equal(t, tt.expectedRepo, repo)
			assert.Equal(t, tt.expectedIP, ip)
		})
	}
}

func TestGetImageRepoIPWithDocker(t *testing.T) {

	const defaultImageRepo = "deploy.bocloud.k8s"

	tests := []struct {
		name                 string
		otherRepo            string
		otherRepoIP          string
		hostIP               string
		imageRepo            string
		mockContainerInspect func() error
		mockGetIP            string
		expectedRepo         string
		expectedIP           string
	}{
		{
			name:        "with other repo containing default image repo",
			otherRepo:   "deploy.bocloud.k8s:5000/test",
			otherRepoIP: testHostIP,
			hostIP:      testLoopbackIP,
			imageRepo:   "deploy.bocloud.k8s",
			mockContainerInspect: func() error {
				return nil
			},
			mockGetIP:    "",
			expectedRepo: "deploy.bocloud.k8s:5000",
			expectedIP:   testHostIP,
		},
		{
			name:        "inspect succeeds with IP",
			otherRepo:   "",
			otherRepoIP: "",
			hostIP:      testLoopbackIP,
			imageRepo:   "registry.test.com",
			mockContainerInspect: func() error {
				return nil
			},
			mockGetIP:    testContainerIP,
			expectedRepo: "registry.test.com:443",
			expectedIP:   testContainerIP,
		},
		{
			name:        "inspect fails with error",
			otherRepo:   "",
			otherRepoIP: "",
			hostIP:      testLoopbackIP,
			imageRepo:   "registry.test.com",
			mockContainerInspect: func() error {
				return fmt.Errorf("inspect failed")
			},
			mockGetIP:    "",
			expectedRepo: "registry.test.com:443",
			expectedIP:   testLoopbackIP,
		},
		{
			name:        "with other repo not containing default image repo",
			otherRepo:   "other.repo:5000/test",
			otherRepoIP: testHostIP,
			hostIP:      testLoopbackIP,
			imageRepo:   "registry.test.com",
			mockContainerInspect: func() error {
				return fmt.Errorf("container not found")
			},
			mockGetIP:    "",
			expectedRepo: "registry.test.com:443",
			expectedIP:   testLoopbackIP,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockDockerClient := &docker.Client{}
			mockDockerPatches := gomonkey.ApplyMethod(mockDockerClient, "GetClient", func(_ *docker.Client) *dockerapi.Client {
				return nil
			})
			defer mockDockerPatches.Reset()

			containerInspectPatches := gomonkey.ApplyFunc((*dockerapi.Client).ContainerInspect,
				func(_ *dockerapi.Client, ctx context.Context, containerID string) (container.InspectResponse, error) {
					if tt.mockContainerInspect() != nil {
						return container.InspectResponse{}, tt.mockContainerInspect()
					}
					return container.InspectResponse{
						ContainerJSONBase: &container.ContainerJSONBase{
							ID: "test-container",
						},
						NetworkSettings: &container.NetworkSettings{
							DefaultNetworkSettings: container.DefaultNetworkSettings{
								IPAddress: tt.mockGetIP,
							},
						},
					}, nil
				})
			defer containerInspectPatches.Reset()

			originalDocker := global.Docker
			global.Docker = mockDockerClient
			defer func() { global.Docker = originalDocker }()

			_ = defaultImageRepo

			repo, ip := getImageRepoIPWithDocker(tt.otherRepo, tt.otherRepoIP, tt.hostIP, tt.imageRepo)

			assert.Equal(t, tt.expectedRepo, repo)
			assert.Equal(t, tt.expectedIP, ip)
		})
	}
}

func TestGenerateRegistriesConfig(t *testing.T) {

	tests := []struct {
		name          string
		mockWriteFile func(string, []byte, os.FileMode) error
		expectedErr   bool
	}{
		{
			name: "generate config success",
			mockWriteFile: func(string, []byte, os.FileMode) error {
				return nil
			},
			expectedErr: false,
		},
		{
			name: "generate config fails",
			mockWriteFile: func(string, []byte, os.FileMode) error {
				return fmt.Errorf("generate config failed")
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			k3sConfig := t.TempDir()
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(os.WriteFile, tt.mockWriteFile)

			err := generateRegistriesConfig("test.repo:443", k3sConfig)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestCheckCurrentCoreDNSConfig(t *testing.T) {

	tests := []struct {
		name        string
		mockExecute func(*exec.CommandExecutor, string, ...string) (string, error)
		expectedErr bool
	}{
		{
			name: "config contains /etc/resolv.conf",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "/etc/resolv.conf nameserver " + testDNSIP, nil
				}
				return "", nil
			},
			expectedErr: false,
		},
		{
			name: "config contains fixed DNS",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "forward . " + testDNSIP, nil
				}
				return "", nil
			},
			expectedErr: false,
		},
		{
			name: "config is empty",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "", nil
				}
				return "", nil
			},
			expectedErr: false,
		},
		{
			name: "execute command fails",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "", fmt.Errorf("command failed")
				}
				return "", nil
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockExecutor := &exec.CommandExecutor{}
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyMethod(mockExecutor, "ExecuteCommandWithCombinedOutput", tt.mockExecute)

			err := checkCurrentCoreDNSConfig()

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestVerifyCoreDNSConfig(t *testing.T) {

	tests := []struct {
		name        string
		mockExecute func(*exec.CommandExecutor, string, ...string) (string, error)
		mockSleep   func(time.Duration)
		expectedErr bool
	}{
		{
			name: "verify config success",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "forward . " + testDNSIP, nil
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: false,
		},
		{
			name: "config still contains /etc/resolv.conf",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "/etc/resolv.conf nameserver " + testDNSIP, nil
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: true,
		},
		{
			name: "fixed DNS not found",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "some random config", nil
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: true,
		},
		{
			name: "execute command fails",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "", fmt.Errorf("command failed")
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockExecutor := &exec.CommandExecutor{}
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyMethod(mockExecutor, "ExecuteCommandWithCombinedOutput", tt.mockExecute)

			patches.ApplyFunc(time.Sleep, tt.mockSleep)
			patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})

			err := verifyCoreDNSConfig()

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestVerifyCoreDNSRunning(t *testing.T) {

	tests := []struct {
		name        string
		mockExecute func(*exec.CommandExecutor, string, ...string) (string, error)
		mockSleep   func(time.Duration)
		expectedErr bool
	}{
		{
			name: "coredns running",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "Running", nil
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: false,
		},
		{
			name: "coredns not running",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "Pending", nil
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: true,
		},
		{
			name: "execute command fails",
			mockExecute: func(executor *exec.CommandExecutor, cmd string, args ...string) (string, error) {
				if len(args) > 0 && args[0] == "get" {
					return "", fmt.Errorf("command failed")
				}
				return "", nil
			},
			mockSleep:   func(time.Duration) {},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockExecutor := &exec.CommandExecutor{}
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyMethod(mockExecutor, "ExecuteCommandWithCombinedOutput", tt.mockExecute)

			patches.ApplyFunc(time.Sleep, tt.mockSleep)
			patches.ApplyFunc(log.SteppedInfo, func(stepName string, args ...any) {})

			err := verifyCoreDNSRunning()

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestContainsFixedDNS(t *testing.T) {

	tests := []struct {
		name     string
		config   string
		expected bool
	}{
		{
			name:     "contains forward . 8.8.8.8",
			config:   "forward . " + testDNSIP,
			expected: true,
		},
		{
			name:     "contains forward . 8.8.4.4",
			config:   "forward . 8.8.4.4",
			expected: true,
		},
		{
			name:     "contains forward . 1.1.1.1",
			config:   "forward . 1.1.1.1",
			expected: true,
		},
		{
			name:     "contains forward . 1.0.0.1",
			config:   "forward . 1.0.0.1",
			expected: true,
		},
		{
			name:     "contains forward . 208.67.222.222",
			config:   "forward . 208.67.222.222",
			expected: true,
		},
		{
			name:     "contains forward . 208.67.220.220",
			config:   "forward . 208.67.220.220",
			expected: true,
		},
		{
			name:     "no fixed DNS",
			config:   "some random config",
			expected: false,
		},
		{
			name:     "empty config",
			config:   "",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := containsFixedDNS(tt.config)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestBuildK3sContainerConfig(t *testing.T) {

	tests := []struct {
		name   string
		hostIP string
	}{
		{
			name:   "build container config",
			hostIP: testLoopbackIP,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := buildK3sContainerConfig(tt.hostIP)

			assert.NotNil(t, config)
			assert.Equal(t, utils.LocalKubernetesName, config.Hostname)
			assert.Contains(t, config.Cmd, "--snapshotter=native")
			assert.Contains(t, config.Cmd, "--service-cidr=100.10.0.0/16")
			assert.Contains(t, config.Cmd, "--cluster-cidr=100.20.0.0/16")
			assert.Contains(t, config.Cmd, fmt.Sprintf("--tls-san=%s", tt.hostIP))
			assert.Contains(t, config.Cmd, fmt.Sprintf("--node-name=%s", utils.LocalKubernetesName))
			assert.NotNil(t, config.ExposedPorts)
			assert.Contains(t, config.ExposedPorts, nat.Port("6443/tcp"))
		})
	}
}

func TestBuildK3sHostConfig(t *testing.T) {

	tests := []struct {
		name           string
		kubernetesPort string
		imageRepo      string
		imageRepoIP    string
	}{
		{
			name:           "build host config",
			kubernetesPort: testPort,
			imageRepo:      "registry.test.com",
			imageRepoIP:    testLoopbackIP,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			config := buildK3sHostConfig(tt.kubernetesPort, tt.imageRepo, tt.imageRepoIP)

			assert.NotNil(t, config)
			assert.NotNil(t, config.PortBindings)
			assert.Contains(t, config.PortBindings, nat.Port("6443/tcp"))
			assert.Equal(t, tt.kubernetesPort, config.PortBindings[nat.Port("6443/tcp")][0].HostPort)
			assert.Equal(t, container.RestartPolicy{Name: "on-failure", MaximumRetryCount: 10}, config.RestartPolicy)
			assert.Contains(t, config.ExtraHosts, fmt.Sprintf("%s:%s", tt.imageRepo, tt.imageRepoIP))
			assert.True(t, config.Privileged)
			assert.Contains(t, config.SecurityOpt, "seccomp=unconfined")
			assert.Contains(t, config.SecurityOpt, "apparmor=unconfined")
			assert.Contains(t, config.SecurityOpt, "label=disable")
			assert.NotNil(t, config.Mounts)
			assert.Len(t, config.Mounts, testFourValue)
		})
	}
}

func TestPrepareK3sEnvironment(t *testing.T) {

	tests := []struct {
		name                string
		cfg                 Config
		localImage          string
		mockEnsureImage     func(image docker.ImageRef, options utils.RetryOptions) error
		mockEnsureRun       func(containerID string) (bool, error)
		mockContainerRemove func(containerID string) error
		mockExists          func(path string) bool
		mockMkdir           func(path string, perm os.FileMode) error
		mockCustomCA        func() error
		mockGetImageRepoIP  func(otherRepo, otherRepoIP, hostIP, imageRepo string) (string, string)
		mockGenerateConfig  func(repo, k3sConfig string) error
		mockIsAvailable     func() bool
		expectedRepo        string
		expectedIP          string
		expectError         bool
	}{
		{
			name: "kubernetes already available",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:          "",
			mockEnsureImage:     func(image docker.ImageRef, options utils.RetryOptions) error { return nil },
			mockEnsureRun:       func(containerID string) (bool, error) { return true, nil },
			mockContainerRemove: func(containerID string) error { return nil },
			mockExists:          func(path string) bool { return true },
			mockMkdir:           func(path string, perm os.FileMode) error { return nil },
			mockCustomCA:        func() error { return nil },
			mockGetImageRepoIP: func(otherRepo, otherRepoIP, hostIP, imageRepo string) (string, string) {
				return "test.repo:443", testLoopbackIP
			},
			mockGenerateConfig: func(repo, k3sConfig string) error { return nil },
			mockIsAvailable:    func() bool { return true },
			expectedRepo:       "",
			expectedIP:         "",
			expectError:        false,
		},
		{
			name: "prepare environment success",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:          "",
			mockEnsureImage:     func(image docker.ImageRef, options utils.RetryOptions) error { return nil },
			mockEnsureRun:       func(containerID string) (bool, error) { return false, nil },
			mockContainerRemove: func(containerID string) error { return nil },
			mockExists:          func(path string) bool { return true },
			mockMkdir:           func(path string, perm os.FileMode) error { return nil },
			mockCustomCA:        func() error { return nil },
			mockGetImageRepoIP: func(otherRepo, otherRepoIP, hostIP, imageRepo string) (string, string) {
				return "test.repo:443", testLoopbackIP
			},
			mockGenerateConfig: func(repo, k3sConfig string) error { return nil },
			mockIsAvailable:    func() bool { return false },
			expectedRepo:       "test.repo:443",
			expectedIP:         testLoopbackIP,
			expectError:        false,
		},
		{
			name: "ensure image fails",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage: "",
			mockEnsureImage: func(image docker.ImageRef, options utils.RetryOptions) error {
				return fmt.Errorf("ensure image failed")
			},
			expectError: true,
		},
		{
			name: "mkdir fails",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockEnsureImage: func(image docker.ImageRef, options utils.RetryOptions) error { return nil },
			mockEnsureRun:   func(containerID string) (bool, error) { return false, nil },
			mockExists:      func(path string) bool { return false },
			mockMkdir: func(path string, perm os.FileMode) error {
				return fmt.Errorf("mkdir failed")
			},
			expectError: true,
		},
		{
			name: "custom CA fails",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:      "",
			mockEnsureImage: func(image docker.ImageRef, options utils.RetryOptions) error { return nil },
			mockEnsureRun:   func(containerID string) (bool, error) { return false, nil },
			mockExists:      func(path string) bool { return true },
			mockMkdir:       func(path string, perm os.FileMode) error { return nil },
			mockCustomCA: func() error {
				return fmt.Errorf("custom CA failed")
			},
			expectError: true,
		},
		{
			name: "generate config fails",
			cfg: Config{
				OtherRepo:      "",
				OtherRepoIP:    "",
				HostIP:         testLoopbackIP,
				ImageRepo:      "registry.test.com",
				ImageRepoPort:  "5000",
				KubernetesPort: testPort,
			},
			localImage:          "",
			mockEnsureImage:     func(image docker.ImageRef, options utils.RetryOptions) error { return nil },
			mockEnsureRun:       func(containerID string) (bool, error) { return false, nil },
			mockContainerRemove: func(containerID string) error { return nil },
			mockExists:          func(path string) bool { return true },
			mockMkdir:           func(path string, perm os.FileMode) error { return nil },
			mockCustomCA:        func() error { return nil },
			mockGetImageRepoIP: func(otherRepo, otherRepoIP, hostIP, imageRepo string) (string, string) {
				return "test.repo:443", testLoopbackIP
			},
			mockGenerateConfig: func(repo, k3sConfig string) error {
				return fmt.Errorf("generate config failed")
			},
			mockIsAvailable: func() bool { return false },
			expectError:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			patches := gomonkey.NewPatches()
			defer patches.Reset()
			patches.ApplyFunc(utils.Exists, tt.mockExists)

			if tt.mockMkdir != nil {
				patches.ApplyFunc(os.MkdirAll, tt.mockMkdir)
			}

			if tt.mockCustomCA != nil {
				patches.ApplyFunc(customCA, tt.mockCustomCA)
			}

			if tt.mockGetImageRepoIP != nil {
				patches.ApplyFunc(getImageRepoIPWithDocker, tt.mockGetImageRepoIP)
			}

			if tt.mockGenerateConfig != nil {
				patches.ApplyFunc(generateRegistriesConfig, tt.mockGenerateConfig)
			}

			if tt.mockIsAvailable != nil {
				patches.ApplyFunc(isKubernetesAvailable, tt.mockIsAvailable)
			}

			mockDockerClient := &docker.Client{}
			dockerPatches := gomonkey.ApplyMethod(mockDockerClient, "EnsureImageExists",
				func(_ *docker.Client, image docker.ImageRef, options utils.RetryOptions) error {
					return tt.mockEnsureImage(image, options)
				})
			defer dockerPatches.Reset()

			dockerPatches.ApplyMethod(mockDockerClient, "EnsureContainerRun",
				func(_ *docker.Client, containerID string) (bool, error) {
					return tt.mockEnsureRun(containerID)
				})

			if tt.mockContainerRemove != nil {
				dockerPatches.ApplyMethod(mockDockerClient, "ContainerRemove",
					func(_ *docker.Client, containerID string) error {
						return tt.mockContainerRemove(containerID)
					})
			}

			originalDocker := global.Docker
			global.Docker = mockDockerClient
			defer func() { global.Docker = originalDocker }()

			repo, ip, err := prepareK3sEnvironment(tt.cfg, tt.localImage)

			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				if tt.expectedRepo != "" {
					assert.Equal(t, tt.expectedRepo, repo)
					assert.Equal(t, tt.expectedIP, ip)
				}
			}
		})
	}
}

func TestWaitForNodeReadyWithK8sClient(t *testing.T) {

	tests := []struct {
		name           string
		setupK8sClient func() *k8s.MockK8sClient
		mockGetNode    func(*k8s.MockK8sClient, string) error
		expectedErr    bool
	}{
		{
			name: "node ready success",
			setupK8sClient: func() *k8s.MockK8sClient {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Spec.Taints = []corev1.Taint{}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				_, _ = fakeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{
					ObjectMeta: metav1.ObjectMeta{Name: "kube-system"},
				}, metav1.CreateOptions{})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				defer patches.Reset()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient
			},
			expectedErr: false,
		},
		{
			name: "node not ready - taints present",
			setupK8sClient: func() *k8s.MockK8sClient {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Spec.Taints = []corev1.Taint{{}}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				defer patches.Reset()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient
			},
			expectedErr: false,
		},
		{
			name: "kube-system namespace not ready",
			setupK8sClient: func() *k8s.MockK8sClient {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Spec.Taints = []corev1.Taint{}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				defer patches.Reset()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient
			},
			expectedErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalK8s := global.K8s
			defer func() { global.K8s = originalK8s }()

			mockClient := tt.setupK8sClient()
			global.K8s = mockClient

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyFunc(time.Sleep, func(time.Duration) {})

			err := waitForNodeReady()

			assert.NoError(t, err)
		})
	}
}

func TestCreateKubeconfigSecretWithK8sClient(t *testing.T) {

	tests := []struct {
		name           string
		kubeconfigData string
		setupK8sClient func() (*k8s.MockK8sClient, *gomonkey.Patches)
		expectedErr    bool
	}{
		{
			name:           "create secret success",
			kubeconfigData: "test-kubeconfig-content",
			setupK8sClient: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				fakeClient := fake.NewSimpleClientset()
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient, patches
			},
			expectedErr: false,
		},
		{
			name:           "create secret fails",
			kubeconfigData: "test-kubeconfig-content",
			setupK8sClient: func() (*k8s.MockK8sClient, *gomonkey.Patches) {
				fakeClient := fake.NewSimpleClientset()
				fakeClient.PrependReactor("create", "secrets", func(action k8sioTesting.Action) (handled bool, ret k8sruntime.Object, err error) {
					return true, nil, fmt.Errorf("secret creation failed")
				})
				mockClient := &k8s.MockK8sClient{}
				patches := gomonkey.NewPatches()
				patches.ApplyMethod(mockClient, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
					return fakeClient
				})
				return mockClient, patches
			},
			expectedErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			originalK8s := global.K8s
			defer func() { global.K8s = originalK8s }()

			mockClient, patches := tt.setupK8sClient()
			defer patches.Reset()

			global.K8s = mockClient

			err := createKubeconfigSecret(tt.kubeconfigData)

			if tt.expectedErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestIsKubernetesAvailableWithRealK8sClient(t *testing.T) {

	tests := []struct {
		name           string
		setupFakeK8s   func() kubernetes.Interface
		expectedResult bool
	}{
		{
			name: "kubernetes available - node ready",
			setupFakeK8s: func() kubernetes.Interface {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Status.Conditions = []corev1.NodeCondition{
					{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
				}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				return fakeClient
			},
			expectedResult: true,
		},
		{
			name: "kubernetes not available - no ready nodes",
			setupFakeK8s: func() kubernetes.Interface {
				fakeClient := fake.NewSimpleClientset()
				node := &corev1.Node{}
				node.Name = utils.LocalKubernetesName
				node.Status.Conditions = []corev1.NodeCondition{
					{Type: corev1.NodeReady, Status: corev1.ConditionFalse},
				}
				_, _ = fakeClient.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{})
				return fakeClient
			},
			expectedResult: false,
		},
		{
			name: "kubernetes not available - no nodes",
			setupFakeK8s: func() kubernetes.Interface {
				return fake.NewSimpleClientset()
			},
			expectedResult: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeClient := tt.setupFakeK8s()

			patches := gomonkey.NewPatches()

			defer patches.Reset()

			patches.ApplyMethod(&k8s.MockK8sClient{}, "GetClient", func(_ *k8s.MockK8sClient) kubernetes.Interface {
				return fakeClient
			})

			patches.ApplyFunc(k8s.NewKubernetesClient, func(kubeConfig string) (k8s.KubernetesClient, error) {
				return &k8s.MockK8sClient{}, nil
			})

			originalK8s := global.K8s
			defer func() { global.K8s = originalK8s }()

			result := isKubernetesAvailable()

			assert.Equal(t, tt.expectedResult, result)
		})
	}
}