//go:build !android

package systemops

import (
	"fmt"
	"net"
	"os"

	"github.com/hashicorp/go-multierror"
	log "github.com/sirupsen/logrus"

	nberrors "github.com/netbirdio/netbird/client/errors"
	"github.com/netbirdio/netbird/client/internal/routemanager/sysctl"
)

const (
	// 1 (default) accepts RAs only while forwarding is off; 2 keeps RA
	// acceptance on regardless, so RA-installed host defaults survive our
	// v6 forwarding flip.
	acceptRAInterfacePath  = "net.ipv6.conf.%s.accept_ra"
	acceptRADefaultPath    = "net.ipv6.conf.default.accept_ra"
	acceptRAProcPathFormat = "/proc/sys/net/ipv6/conf/%s/accept_ra"
)

// EnableV6IPForwarding bumps accept_ra=2 on host v6 interfaces before flipping
// forwarding=1, so RA-installed host defaults survive. Returns the prior values
// of sysctls we actually changed; entries already at the target are omitted.
func EnableV6IPForwarding(wgIfaceName string) (map[string]int, error) {
	saved := map[string]int{}
	bumpAcceptRA(saved, wgIfaceName)

	oldVal, err := sysctl.Set(ipv6ForwardingPath, 1, false)
	if err != nil {
		return saved, err
	}
	if oldVal != 1 {
		saved[ipv6ForwardingPath] = oldVal
	}
	return saved, nil
}

// DisableV6IPForwarding restores what EnableV6IPForwarding captured.
func DisableV6IPForwarding(saved map[string]int) error {
	var result *multierror.Error
	for key, value := range saved {
		if _, err := sysctl.Set(key, value, false); err != nil {
			result = multierror.Append(result, fmt.Errorf("restore %s: %w", key, err))
		}
	}
	return nberrors.FormatErrorOrNil(result)
}

func bumpAcceptRA(saved map[string]int, wgIfaceName string) {
	// Also bump conf.default so interfaces created while forwarding is on
	// (hotplug, new Wi-Fi/dock) inherit accept_ra=2 and keep accepting RAs.
	bumpAcceptRAKey(saved, acceptRADefaultPath)

	interfaces, err := net.Interfaces()
	if err != nil {
		log.Warnf("list interfaces for accept_ra: %v", err)
		return
	}
	for _, intf := range interfaces {
		if intf.Name == "lo" || intf.Name == wgIfaceName {
			continue
		}
		bumpAcceptRAForInterface(saved, intf.Name)
	}
}

func bumpAcceptRAForInterface(saved map[string]int, name string) {
	// Build procfs path from name, not the dotted key: VLAN names like eth0.100.
	if _, err := os.Stat(fmt.Sprintf(acceptRAProcPathFormat, name)); err != nil {
		return
	}
	bumpAcceptRAKey(saved, fmt.Sprintf(acceptRAInterfacePath, sysctl.EscapeInterfaceName(name)))
}

func bumpAcceptRAKey(saved map[string]int, key string) {
	// onlyIfOne=true: leave admin overrides (0, 2) alone.
	oldVal, err := sysctl.Set(key, 2, true)
	if err != nil {
		log.Warnf("bump %s: %v", key, err)
		return
	}
	// With onlyIfOne, a write only happened when the old value was 1; values
	// left untouched (0, 2) must not be recorded for restore.
	if oldVal == 1 {
		saved[key] = oldVal
	}
}
