package kernelparameters

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/tep/pkg/apis/apiserver"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"github.com/pingcap/tep/pkg/utils"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/endpoints/request"
)

var kernelParameterList = []apiserver.KernelParameter{
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.core.somaxconn",
		},
		Recommend:   "32768",
		Description: "Limit of socket listen() backlog, known in userspace as SOMAXCONN. Defaults to 4096. (Was 128 before linux-5.4)",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "vm.swappiness",
		},
		Recommend:   "0",
		Description: "Swappiness is a property for the Linux kernel that changes the balance between swapping out runtime memory, as opposed to dropping pages from the system page cache. Swappiness can be set to values between 0 and 100 inclusive. A low value means the kernel will try to avoid swapping as much as possible where a higher value instead will make the kernel aggressively try to use swap space. The default value is 60, and for most desktop systems, setting it to 100 may affect the overall performance, whereas setting it lower (even 0) may improve interactivity (decreasing response latency.)",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.tcp_syncookies",
		},
		Recommend:   "0",
		Description: "syncookies seriously violate TCP protocol, do not allow to use TCP extensions, can result in serious degradation of some services (f.e. SMTP relaying), visible not by you, but your clients and relays, contacting you. While you see SYN flood warnings in logs not being really flooded, your server is seriously misconfigured.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.ip_forward",
		},
		Recommend:   "1",
		Description: "This variable is special, its change resets all configuration parameters to their default state (RFC1122 for hosts, RFC1812 for routers)",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fs.file-max",
		},
		Recommend:   "1000000",
		Description: "Historically,the kernel was able to allocate file handles dynamically, but not to free them again. The three values in file-nr denote the number of allocated file handles, the number of allocated but unused file handles, and the maximum number of file handles. Linux 2.6 always reports 0 as the number of free file handles -- this is not an error, it just means that the number of allocated file handles exactly matches the number of used file handles.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fs.inotify.max_user_watches",
		},
		Recommend:   "1048576",
		Description: "This specifies an upper limit on the number of watches that can be created per real user ID.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "fs.inotify.max_user_instances",
		},
		Recommend:   "128",
		Description: "This specifies an upper limit on the number of inotify instances that can be created per real user ID.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.conf.all.rp_filter",
		},
		Recommend:   "1",
		Description: "Current recommended practice in RFC3704 is to enable strict mode to prevent IP spoofing from DDos attacks. If using asymmetric routing or other complicated routing, then loose mode is recommended.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.neigh.default.gc_thresh1",
		},
		Recommend:   "80000",
		Description: "Minimum number of entries to keep.  Garbage collector will not purge entries if there are fewer than this number.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.neigh.default.gc_thresh2",
		},
		Recommend:   "90000",
		Description: "Threshold when garbage collector becomes more aggressive about purging entries. Entries older than 5 seconds will be cleared when over this number.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.ipv4.neigh.default.gc_thresh3",
		},
		Recommend:   "100000",
		Description: "Maximum number of non-PERMANENT neighbor entries allowed.  Increase this when using large numbers of interfaces and when communicating with large numbers of directly-connected peers.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.bridge.bridge-nf-call-iptables",
		},
		Recommend:   "1",
		Description: "1 : pass bridged IPv4 traffic to iptables' chains.  0 : disable this.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.bridge.bridge-nf-call-arptables",
		},
		Recommend:   "1",
		Description: "1 : pass bridged ARP traffic to arptables' FORWARD chain. 0 : disable this.",
	},
	apiserver.KernelParameter{
		ObjectMeta: metav1.ObjectMeta{
			Name: "net.bridge.bridge-nf-call-ip6tables",
		},
		Recommend:   "1",
		Description: "1 : pass bridged IPv6 traffic to ip6tables' chains. 0 : disable this.",
	},
}

type Storage struct {
	store         *store.Store
	groupResource schema.GroupResource
}

func NewStorage(store *store.Store, groupResource schema.GroupResource) *Storage {
	return &Storage{
		store:         store,
		groupResource: groupResource,
	}
}

func (s *Storage) Kind() string {
	return "KernelParameter"
}

func (s *Storage) New() runtime.Object {
	return &apiserver.KernelParameter{}
}

func (s *Storage) List(ctx context.Context, options *metainternalversion.ListOptions, extraOptions runtime.Object) (runtime.Object, error) {
	var err, msg error
	var podList *corev1.PodList
	var pod corev1.Pod
	var podName, containerName, stdout, stderr string
	var result []string
	var name string
	var page, itemsPerPage, itemTotal int
	var kernelParameters []apiserver.KernelParameter

	requestInfo, found := request.RequestInfoFrom(ctx)
	if !found {
		return nil, errors.NewBadRequest("no RequestInfo found in the context")
	}
	name = requestInfo.Name
	extraOption := extraOptions.(*apiserver.ListOptions)

	if extraOption.ItemsPerPage != 0 {
		itemsPerPage = extraOption.ItemsPerPage
	} else {
		itemsPerPage = 10
	}

	if extraOption.Page != 0 {
		page = extraOption.Page
	} else {
		page = 1
	}

	itemTotal = len(kernelParameterList)

	kernelParameters = utils.Pagination(page, itemsPerPage, kernelParameterList).([]apiserver.KernelParameter)

	sysctls := "sysctl -n "
	for _, val := range kernelParameters {
		sysctls = fmt.Sprintf("%s %s", sysctls, val.Name)
	}
	labelSelector := "app=node-exporter"
	fieldSelector := fmt.Sprintf("spec.nodeName=%s", name)

	if podList, err = s.store.Controller().ListPods(s.store.K8sPromNamespace, metav1.ListOptions{FieldSelector: fieldSelector, LabelSelector: labelSelector}); err != nil {
		if errors.IsNotFound(err) {
			return nil, errors.NewNotFound(s.groupResource, fmt.Sprintf("node_exporter pod is not found in node[%s], namespace[%s]", name, s.store.K8sPromNamespace))
		}
		msg = fmt.Errorf("failed to get node_exporter pod in namespace[%s], node[%s]", s.store.K8sPromNamespace, name)
		return nil, errors.NewInternalError(msg)
	}

	if len(podList.Items) > 1 {
		msg = fmt.Errorf("more than one node_exporter pod in namespace[%s], node[%s]", s.store.K8sPromNamespace, name)
		return nil, errors.NewInternalError(msg)
	} else if len(podList.Items) == 0 {
		return nil, errors.NewNotFound(s.groupResource, fmt.Sprintf("node_exporter pod is not found in node[%s], namespace[%s]", name, s.store.K8sPromNamespace))
	}

	pod = podList.Items[0]

	podName = pod.Name
	containerName = pod.Spec.Containers[0].Name
	stdout, stderr, err = s.store.ExecToPodThroughAPI(sysctls, containerName, podName, s.store.K8sPromNamespace, nil)
	if err != nil {
		msg = fmt.Errorf("failed to execute command in namespace[%s], pod[%s], err[%v]", s.store.K8sPromNamespace, podName, err)
		return nil, errors.NewInternalError(msg)
	}

	if strings.TrimSpace(stderr) != "" {
		msg = fmt.Errorf("exec command occurs error[%s] in namespace[%s], pod[%s]", stderr, s.store.K8sPromNamespace, podName)
		return nil, errors.NewInternalError(msg)
	}

	result = strings.Split(stdout, "\n")

	if len(result)-1 != len(kernelParameters) {
		msg = fmt.Errorf("exec output is not correct. stdout[%s] stdout len[%d] parameterlist len[%d]", stdout, len(result), len(kernelParameters))
		return nil, errors.NewInternalError(msg)
	}

	for i := range kernelParameters {
		kernelParameters[i].Value = result[i]
	}

	return &apiserver.KernelParameterList{
		Items: kernelParameters,
		Total: int64(itemTotal),
	}, nil
}

func (s *Storage) NamespaceScoped() bool {
	return false
}

func (s *Storage) NewExtraListOptions() runtime.Object {
	return &apiserver.ListOptions{}
}

func (s *Storage) NewList() runtime.Object {
	return &apiserver.KernelParameterList{}
}
