package utils

import (
	"fmt"
	"os"
	"strconv"
)

const (
	// From what I can see there are no IPv6 equivalents for the below options, so we only consider IPv6 here
	// Network Services Configuration Paths
	IPv4IPVSConntrack        = "net/ipv4/vs/conntrack"
	IPv4IPVSExpireNodestConn = "net/ipv4/vs/expire_nodest_conn"
	IPv4IPVSExpireQuiescent  = "net/ipv4/vs/expire_quiescent_template"
	IPv4IPVSConnReuseMode    = "net/ipv4/vs/conn_reuse_mode"
	IPv4IPVSSloppyTCP        = "net/ipv4/vs/sloppy_tcp"
	IPv4ConfAllArpIgnore     = "net/ipv4/conf/all/arp_ignore"
	IPv4ConfAllArpAnnounce   = "net/ipv4/conf/all/arp_announce"
	IPv6ConfAllDisableIPv6   = "net/ipv6/conf/all/disable_ipv6"

	// Network Routes Configuration Paths
	BridgeNFCallIPTables  = "net/bridge/bridge-nf-call-iptables"
	BridgeNFCallIP6Tables = "net/bridge/bridge-nf-call-ip6tables"

	// Template Configuration Paths
	IPv4ConfRPFilterTemplate = "net/ipv4/conf/%s/rp_filter"
)

type SysctlError struct {
	additionalInfo string
	err            error
	option         string
	hasValue       bool
	value          int
	fatal          bool
}

// Error return the error as string
func (e *SysctlError) Error() string {
	value := ""
	if e.hasValue {
		value = fmt.Sprintf("=%d", e.value)
	}
	return fmt.Sprintf("Sysctl %s%s : %s (%s)", e.option, value, e.err, e.additionalInfo)
}

// IsFatal was the error fatal and reason to exit kube-router
func (e *SysctlError) IsFatal() bool {
	return e.fatal
}

// Unwrap allows us to unwrap an error showing the original error
func (e *SysctlError) Unwrap() error {
	return e.err
}

type SysctlConfig struct {
	name  string
	value int8
}

func (n *SysctlConfig) CachedVal() int8 {
	return n.value
}

func (n *SysctlConfig) WriteVal(val int8) *SysctlError {
	err := SetSysctl(n.name, int(val))
	if err != nil {
		return err
	}
	n.value = val
	return nil
}

func sysctlStat(path string, hasValue bool, value int) (string, *SysctlError) {
	sysctlPath := fmt.Sprintf("/proc/sys/%s", path)
	if _, err := os.Stat(sysctlPath); err != nil {
		if os.IsNotExist(err) {
			return sysctlPath, &SysctlError{
				"option not found, Does your kernel version support this feature?",
				err, path, hasValue, value, false}
		}
		return sysctlPath, &SysctlError{"path existed, but could not be stat'd", err, path, hasValue, value, true}
	}
	return sysctlPath, nil
}

// GetSysctlSingleTemplate gets a sysctl value by first formatting the PathTemplate parameter with the substitute string
// and then getting the sysctl value and converting it into a string
func GetSysctlSingleTemplate(pathTemplate string, substitute string) (string, *SysctlError) {
	actualPath := fmt.Sprintf(pathTemplate, substitute)
	return GetSysctl(actualPath)
}

// GetSysctl gets a sysctl value
func GetSysctl(path string) (string, *SysctlError) {
	sysctlPath, err := sysctlStat(path, false, 0)
	if err != nil {
		return "", err
	}
	buf, readErr := os.ReadFile(sysctlPath)
	if readErr != nil {
		return "", &SysctlError{"path could not be read", err, path, false, 0, true}
	}
	return string(buf), nil
}

// SetSysctlSingleTemplate sets a sysctl value by first formatting the PathTemplate parameter with the substitute string
// and then setting the sysctl to the value parameter
func SetSysctlSingleTemplate(pathTemplate string, substitute string, value int) *SysctlError {
	actualPath := fmt.Sprintf(pathTemplate, substitute)
	return SetSysctl(actualPath, value)
}

// SetSysctl sets a sysctl value
func SetSysctl(path string, value int) *SysctlError {
	sysctlPath, err := sysctlStat(path, true, value)
	if err != nil {
		return err
	}
	writeErr := os.WriteFile(sysctlPath, []byte(strconv.Itoa(value)), 0640)
	if writeErr != nil {
		return &SysctlError{"path could not be set", err, path, true, value, true}
	}
	return nil
}
