// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package kernel

import (
	"path"
	"path/filepath"
	"strings"

	"github.com/siderolabs/talos/pkg/machinery/constants"
	"github.com/siderolabs/talos/pkg/machinery/imager/quirks"
)

const (
	// Sysfs defines prefix for sysfs kernel params.
	Sysfs = "sys"
	// Sysctl defines prefix for sysctl kernel params.
	Sysctl = "proc.sys"
)

// DefaultArgs returns the Talos default kernel commandline options.
func DefaultArgs(quirks quirks.Quirks) []string {
	var result []string

	if !quirks.DropInitOnAllocInArgs() {
		result = append(
			result,
			"init_on_alloc=1",
		)
	}

	result = append(
		result,
		"slab_nomerge=",
		"pti=on",
		"consoleblank=0",
	)

	if !quirks.NvmeCoreIoTimeoutAWSOnly() {
		// AWS recommends setting the nvme_core.io_timeout to the highest value possible.
		// See https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html.
		result = append(
			result,
			"nvme_core.io_timeout=4294967295",
		)
	}

	// Disable rate limited printk
	result = append(
		result,
		"printk.devkmsg=on",
	)

	if quirks.SupportsIMA() {
		// Enable IMAs for integrity measurement
		result = append(
			result,
			"ima_template=ima-ng",
			"ima_appraise=fix",
			"ima_hash=sha512",
		)
	}

	if quirks.SupportsSELinux() {
		result = append(result, constants.KernelParamSELinux+"=1")
	}

	if quirks.SupportsDisablingModuleSignatureVerification() {
		result = append(result, constants.KernelParamEnforceModuleSigVerify+"=1") // see https://github.com/siderolabs/talos/issues/11989
	}

	if quirks.ProcMemOverrideNever() {
		result = append(result, "proc_mem.force_override=never")
	}

	return result
}

// SecureBootArgs returns the kernel commandline options required for secure boot.
func SecureBootArgs(quirks.Quirks) []string {
	return []string{
		"lockdown=confidentiality",
	}
}

// Param represents a kernel system property.
type Param struct {
	Key   string
	Value string
}

// Path returns the path to the systctl file under /proc/sys or /sys.
func (prop *Param) Path() string {
	// From: https://man7.org/linux/man-pages/man5/sysctl.d.5.html
	//
	// Note that either "/" or "."  may be used as separators within
	// sysctl variable names. If the first separator is a slash,
	// remaining slashes and dots are left intact. If the first
	// separator is a dot, dots and slashes are interchanged.
	// "kernel.domainname=foo" and "kernel/domainname=foo" are
	// equivalent and will cause "foo" to be written to
	// /proc/sys/kernel/domainname. Either
	// "net.ipv4.conf.enp3s0/200.forwarding" or
	// "net/ipv4/conf/enp3s0.200/forwarding" may be used to refer to
	// /proc/sys/net/ipv4/conf/enp3s0.200/forwarding
	//
	// detect the first separator, either '.' or '/'
	// according to the sysctl man page, if the first separator is '/', we keep slashes intact,
	// otherwise we convert dots to slashes
	keyPath := prop.Key
	prefix := ""

	// trim standard prefix
	for _, stdPrefix := range []string{Sysctl, Sysfs} {
		if strings.HasPrefix(prop.Key, stdPrefix+".") {
			keyPath = keyPath[len(stdPrefix)+1:]
			prefix = stdPrefix

			break
		}
	}

	firstSepIndex := strings.IndexAny(keyPath, "./")
	// if the first separator is a dot, remap '.' to '/', and '/' to '.'
	if firstSepIndex != -1 && keyPath[firstSepIndex] == '.' {
		keyPath = strings.Map(
			func(r rune) rune {
				switch r {
				case '.':
					return '/'
				case '/':
					return '.'
				default:
					return r
				}
			},
			keyPath,
		)
	}

	return path.Clean("/" + filepath.Join(strings.ReplaceAll(prefix, ".", "/"), keyPath))
}
