//go:build linux

package namespaces

import (
	"errors"
	"fmt"
	"syscall"

	"github.com/vishvananda/netlink"
)

// InterfacePair represents a pair of virtual ethernets that are connected to
// each other. these are used to connect a network namespace to the outside
// world.
type InterfacePair struct {
	nethandler NetlinkHandler
	prefix     string
	in         netlink.Link
	out        netlink.Link
	cfg        Configuration
}

// SetExternalIP assigns an ip address to the interface living in the default
// namespace (outside interface).
func (p *InterfacePair) SetExternalIP(outaddr string) error {
	addr, err := p.nethandler.ParseAddr(outaddr)
	if err != nil {
		return fmt.Errorf("error parsing ip: %w", err)
	}

	if err := p.nethandler.AddrAdd(p.out, addr); err != nil {
		return fmt.Errorf("error assigning ip: %w", err)
	}

	if err := p.nethandler.LinkSetUp(p.out); err != nil {
		return fmt.Errorf("error bringing up: %w", err)
	}
	return nil
}

// Close deletes the interface pair. by deleting one of the interfaces, the
// other is deleted as well.
func (p *InterfacePair) Close() error {
	for _, ifc := range []netlink.Link{p.in, p.out} {
		if err := p.nethandler.LinkDel(ifc); err != nil {
			var scerr syscall.Errno
			if errors.As(err, &scerr) && scerr == syscall.ENODEV {
				continue
			}
			return fmt.Errorf("error deleting %s: %w", ifc.Attrs().Name, scerr)
		}
	}
	return nil
}

// Setup sets up the interface pair. this function will create the veth pair
// and bring the interfaces up.
func (p *InterfacePair) Setup() (err error) {
	in := fmt.Sprintf("%s-in", p.prefix)
	out := fmt.Sprintf("%s-out", p.prefix)
	veth := &netlink.Veth{
		LinkAttrs: netlink.LinkAttrs{Name: in},
		PeerName:  out,
	}

	p.cfg.Logf("creating interface pair %q and %q", in, out)
	if err := p.nethandler.LinkAdd(veth); err != nil {
		return fmt.Errorf("error creating veth pair: %w", err)
	}

	if p.in, err = p.nethandler.LinkByName(in); err != nil {
		return fmt.Errorf("error finding %s: %w", in, err)
	}

	if p.out, err = p.nethandler.LinkByName(out); err != nil {
		return fmt.Errorf("error finding %s: %w", out, err)
	}
	return nil
}

// NewInterfacePair creates a pair of connected virtual ethernets. interfaces
// are named `prefix-in` and `prefix-out`.
func NewInterfacePair(prefix string, options ...Option) *InterfacePair {
	config := NewConfiguration(options...)
	return &InterfacePair{
		nethandler: NetlinkHandle{},
		prefix:     prefix,
		cfg:        config,
	}
}
