/*
Copyright 2017 The Kubernetes 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 netsh

import (
	"net"
	"os"
	"testing"

	"k8s.io/utils/exec"
	fakeexec "k8s.io/utils/exec/testing"

	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
)

func fakeCommonRunner() *runner {
	fakeCmd := fakeexec.FakeCmd{
		CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
			// Success
			func() ([]byte, error) {
				return []byte{}, nil
			},
			// utilexec.ExitError exists, and status is not 0
			func() ([]byte, error) {
				return nil, &fakeexec.FakeExitError{Status: 1}
			},
			// utilexec.ExitError exists, and status is 0
			func() ([]byte, error) {
				return nil, &fakeexec.FakeExitError{Status: 0}
			},
			// other error exists
			func() ([]byte, error) {
				return nil, errors.New("not ExitError")
			},
		},
	}

	return &runner{
		exec: &fakeexec.FakeExec{
			CommandScript: []fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
				},
			},
		},
	}
}

func TestEnsurePortProxyRule(t *testing.T) {
	runner := fakeCommonRunner()

	tests := []struct {
		name           string
		arguments      []string
		expectedResult bool
		expectedError  bool
	}{
		{"Success", []string{"ensure-port-proxy-rule"}, true, false},
		{"utilexec.ExitError exists, and status is not 0", []string{"ensure-port-proxy-rule"}, false, false},
		{"utilexec.ExitError exists, and status is 0", []string{"ensure-port-proxy-rule"}, false, true},
		{"other error exists", []string{"ensure-port-proxy-rule"}, false, true},
	}

	for _, test := range tests {
		result, err := runner.EnsurePortProxyRule(test.arguments)
		if test.expectedError {
			assert.Errorf(t, err, "Failed to test: %s", test.name)
		} else {
			if err != nil {
				assert.NoErrorf(t, err, "Failed to test: %s", test.name)
			} else {
				assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name)
			}
		}
	}

}

func TestDeletePortProxyRule(t *testing.T) {
	runner := fakeCommonRunner()

	tests := []struct {
		name          string
		arguments     []string
		expectedError bool
	}{
		{"Success", []string{"delete-port-proxy-rule"}, false},
		{"utilexec.ExitError exists, and status is not 0", []string{"delete-port-proxy-rule"}, true},
		{"utilexec.ExitError exists, and status is 0", []string{"delete-port-proxy-rule"}, false},
		{"other error exists", []string{"delete-port-proxy-rule"}, true},
	}

	for _, test := range tests {
		err := runner.DeletePortProxyRule(test.arguments)
		if test.expectedError {
			assert.Errorf(t, err, "Failed to test: %s", test.name)
		} else {
			assert.NoErrorf(t, err, "Failed to test: %s", test.name)
		}
	}
}

func TestEnsureIPAddress(t *testing.T) {
	tests := []struct {
		name           string
		arguments      []string
		ip             net.IP
		fakeCmdAction  []fakeexec.FakeCommandAction
		expectedError  bool
		expectedResult bool
	}{
		{
			"IP address exists",
			[]string{"delete-port-proxy-rule"},
			net.IPv4(10, 10, 10, 20),
			[]fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil
							},
						},
					}, cmd, args...)
				},
			},
			false,
			true,
		},

		{
			"IP address not exists, but set successful(find it in the second time)",
			[]string{"ensure-ip-address"},
			net.IPv4(10, 10, 10, 20),
			[]fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address not exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10"), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// Success to set ip
							func() ([]byte, error) {
								return []byte(""), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address still not exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10"), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil
							},
						},
					}, cmd, args...)
				},
			},
			false,
			true,
		},
		{
			"IP address not exists, utilexec.ExitError exists, but status is not 0)",
			[]string{"ensure-ip-address"},
			net.IPv4(10, 10, 10, 20),
			[]fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address not exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10"), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// Failed to set ip, utilexec.ExitError exists, and status is not 0
							func() ([]byte, error) {
								return nil, &fakeexec.FakeExitError{Status: 1}
							},
						},
					}, cmd, args...)
				},
			},
			false,
			false,
		},
		{
			"IP address not exists, utilexec.ExitError exists, and status is 0)",
			[]string{"ensure-ip-address"},
			net.IPv4(10, 10, 10, 20),
			[]fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address not exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10"), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// Failed to set ip, utilexec.ExitError exists, and status is 0
							func() ([]byte, error) {
								return nil, &fakeexec.FakeExitError{Status: 0}
							},
						},
					}, cmd, args...)
				},
			},
			true,
			false,
		},
		{
			"IP address not exists, and error is not utilexec.ExitError)",
			[]string{"ensure-ip-address"},
			net.IPv4(10, 10, 10, 20),
			[]fakeexec.FakeCommandAction{
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// IP address not exists
							func() ([]byte, error) {
								return []byte("IP Address:10.10.10.10"), nil
							},
						},
					}, cmd, args...)
				},
				func(cmd string, args ...string) exec.Cmd {
					return fakeexec.InitFakeCmd(&fakeexec.FakeCmd{
						CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
							// Failed to set ip, other error exists
							func() ([]byte, error) {
								return nil, errors.New("not ExitError")
							},
						},
					}, cmd, args...)
				},
			},
			true,
			false,
		},
	}

	for _, test := range tests {
		runner := New(&fakeexec.FakeExec{CommandScript: test.fakeCmdAction})
		result, err := runner.EnsureIPAddress(test.arguments, test.ip)
		if test.expectedError {
			assert.Errorf(t, err, "Failed to test: %s", test.name)
		} else {
			if err != nil {
				assert.NoErrorf(t, err, "Failed to test: %s", test.name)
			} else {
				assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name)
			}
		}
	}
}

func TestDeleteIPAddress(t *testing.T) {
	runner := fakeCommonRunner()

	tests := []struct {
		name          string
		arguments     []string
		expectedError bool
	}{
		{"Success", []string{"delete-ip-address"}, false},
		{"utilexec.ExitError exists, and status is not 0", []string{"delete-ip-address"}, true},
		{"utilexec.ExitError exists, and status is 0", []string{"delete-ip-address"}, false},
		{"other error exists", []string{"delete-ip-address"}, true},
	}

	for _, test := range tests {
		err := runner.DeleteIPAddress(test.arguments)
		if test.expectedError {
			assert.Errorf(t, err, "Failed to test: %s", test.name)
		} else {
			assert.NoErrorf(t, err, "Failed to test: %s", test.name)
		}
	}
}

func TestGetInterfaceToAddIP(t *testing.T) {
	// backup env 'INTERFACE_TO_ADD_SERVICE_IP'
	backupValue := os.Getenv("INTERFACE_TO_ADD_SERVICE_IP")
	// recover env
	defer os.Setenv("INTERFACE_TO_ADD_SERVICE_IP", backupValue)

	tests := []struct {
		name           string
		envToBeSet     string
		expectedResult string
	}{
		{"env_value_is_empty", "", "vEthernet (HNS Internal NIC)"},
		{"env_value_is_not_empty", "eth0", "eth0"},
	}

	fakeExec := fakeexec.FakeExec{
		CommandScript: []fakeexec.FakeCommandAction{},
	}
	netsh := New(&fakeExec)

	for _, test := range tests {
		os.Setenv("INTERFACE_TO_ADD_SERVICE_IP", test.envToBeSet)
		result := netsh.GetInterfaceToAddIP()

		assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name)
	}
}

func TestRestore(t *testing.T) {
	runner := New(&fakeexec.FakeExec{
		CommandScript: []fakeexec.FakeCommandAction{},
	})

	result := runner.Restore([]string{})
	assert.NoErrorf(t, result, "The return value must be nil")
}

func TestCheckIPExists(t *testing.T) {
	fakeCmd := fakeexec.FakeCmd{
		CombinedOutputScript: []fakeexec.FakeCombinedOutputAction{
			// Error exists
			func() ([]byte, error) {
				return nil, &fakeexec.FakeExitError{Status: 1}
			},
			// IP address string is empty
			func() ([]byte, error) {
				return []byte(""), nil
			},
			// "IP Address:" field not exists
			func() ([]byte, error) {
				return []byte("10.10.10.10"), nil
			},
			// IP not exists
			func() ([]byte, error) {
				return []byte("IP Address:10.10.10.10"), nil
			},
			// IP exists
			func() ([]byte, error) {
				return []byte("IP Address:10.10.10.10\nIP Address:10.10.10.20"), nil
			},
		},
	}
	fakeExec := fakeexec.FakeExec{
		CommandScript: []fakeexec.FakeCommandAction{
			func(cmd string, args ...string) exec.Cmd {
				return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
			},
			func(cmd string, args ...string) exec.Cmd {
				return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
			},
			func(cmd string, args ...string) exec.Cmd {
				return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
			},
			func(cmd string, args ...string) exec.Cmd {
				return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
			},
			func(cmd string, args ...string) exec.Cmd {
				return fakeexec.InitFakeCmd(&fakeCmd, cmd, args...)
			},
		},
	}
	fakeRunner := &runner{
		exec: &fakeExec,
	}

	tests := []struct {
		name           string
		ipToCheck      string
		arguments      []string
		expectedError  bool
		expectedResult bool
	}{
		{"Error exists", "10.10.10.20", []string{"check-IP-exists"}, true, false},
		{"IP address string is empty", "10.10.10.20", []string{"check-IP-exists"}, false, false},
		{"'IP Address:' field not exists", "10.10.10.20", []string{"check-IP-exists"}, false, false},
		{"IP not exists", "10.10.10.20", []string{"check-IP-exists"}, false, false},
		{"IP exists", "10.10.10.20", []string{"check-IP-exists"}, false, true},
	}

	for _, test := range tests {
		result, err := checkIPExists(test.ipToCheck, test.arguments, fakeRunner)
		if test.expectedError {
			assert.Errorf(t, err, "Failed to test: %s", test.name)
		} else {
			assert.EqualValuesf(t, test.expectedResult, result, "Failed to test: %s", test.name)
		}
	}
}

func TestGetIP(t *testing.T) {
	testcases := []struct {
		showAddress   string
		expectAddress string
	}{
		{
			showAddress:   "IP 地址:                           10.96.0.2",
			expectAddress: "10.96.0.2",
		},
		{
			showAddress:   "IP Address:                           10.96.0.3",
			expectAddress: "10.96.0.3",
		},
		{
			showAddress:   "IP Address:10.96.0.4",
			expectAddress: "10.96.0.4",
		},
	}

	for _, tc := range testcases {
		address := getIP(tc.showAddress)
		if address != tc.expectAddress {
			t.Errorf("expected address=%q, got %q", tc.expectAddress, address)
		}
	}
}
