package netlink

import (
	"antoine-roux.tk/projects/go/firecracker-netns/internal/sysctl"
	"crypto/rand"
	"fmt"
	"github.com/coreos/go-iptables/iptables"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"
	"net"
	"os/user"
	"strconv"
)

// inspired by https://github.com/firecracker-microvm/firecracker-go-sdk/blob/main/cni/internal/netlink.go#L197

type Tap struct {
	Link             netlink.Link
	Ip               net.IPNet
	externalLinkName string
}

// CreateTap set up a tap device with random number
// this tap is linked with routing to given externalLinkName
func CreateTap(tapNetwork net.IPNet, externalLinkName string) (*Tap, error) {
	tapName, err := randomTapName()
	if err != nil {
		return nil, err
	}

	// Get current user information
	current, err := user.Current()
	if err != nil {
		return nil, err
	}
	gid, err := strconv.Atoi(current.Uid)
	if err != nil {
		return nil, err
	}
	uid, err := strconv.Atoi(current.Gid)
	if err != nil {
		return nil, err
	}

	tapLinkAttrs := netlink.NewLinkAttrs()
	tapLinkAttrs.Name = tapName
	tapLink := &netlink.Tuntap{
		LinkAttrs: tapLinkAttrs,

		// We want a tap device (L2) as opposed to a tun (L3)
		Mode: netlink.TUNTAP_MODE_TAP,

		// Firecracker does not support multiqueue tap devices at this time:
		// https://github.com/firecracker-microvm/firecracker/issues/750
		Queues: 1,

		// single queue tap device
		// parse vnet headers added by the vm's virtio_net implementation
		Flags: unix.IFF_TAP | netlink.TUNTAP_ONE_QUEUE | netlink.TUNTAP_VNET_HDR,

		Owner: uint32(uid),
		Group: uint32(gid),
	}

	err = netlink.LinkAdd(tapLink)
	if err != nil {
		return nil, err
	}

	for _, tapFd := range tapLink.Fds {
		err := tapFd.Close()
		if err != nil {
			return nil, err
		}
	}

	// Re-fetch the container link to get its creation-time parameters, e.g. index and mac
	fmt.Println(tapName)
	tap, err := netlink.LinkByName(tapName)
	if err != nil {
		netlink.LinkDel(tapLink)
		return nil, err
	}

	// Ip tapAddr add <tap Network> dev tap0
	err = netlink.AddrAdd(tap, &netlink.Addr{
		IPNet: &tapNetwork,
	})
	if err != nil {
		return nil, err
	}

	err = netlink.LinkSetUp(tap)
	if err != nil {
		return nil, err
	}

	err = sysctl.Set(fmt.Sprintf("net.ipv4.conf.%s.proxy_arp", tap.Attrs().Name), "1")
	if err != nil {
		return nil, err
	}

	// echo 1 > /proc/sys/net/ipv4/ip_forward
	err = sysctl.Set("net.ipv4.ip_forward", "1")
	if err != nil {
		return nil, err
	}

	ipt, err := iptables.New()
	if err != nil {
		return nil, err
	}

	// sudo iptables -t nat -I POSTROUTING 1 -o vethee574a50 -j MASQUERADE --source 192.168.0.3/24
	err = ipt.InsertUnique("nat", "POSTROUTING", 1, "-o", externalLinkName, "-j", "MASQUERADE", "--source", tapNetwork.String())
	if err != nil {
		return nil, err
	}

	// sudo iptables -I FORWARD 1 -o tap0 -i vethee574a50 -j ACCEPT
	err = ipt.InsertUnique("filter", "FORWARD", 1, "-o", externalLinkName, "-i", tap.Attrs().Name, "-j", "ACCEPT")
	if err != nil {
		return nil, err
	}

	// sudo iptables -I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
	err = ipt.InsertUnique("filter", "FORWARD", 1, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT")
	if err != nil {
		return nil, err
	}

	return &Tap{tap, tapNetwork, externalLinkName}, nil
}

// randomTapName returns string "tap" with random prefix (hashed from entropy)
func randomTapName() (string, error) {
	entropy := make([]byte, 4)
	_, err := rand.Read(entropy)
	if err != nil {
		return "", fmt.Errorf("failed to generate random tap name: %v", err)
	}

	// NetworkManager (recent versions) will ignore veth devices that start with "veth"
	return fmt.Sprintf("tap%x", entropy), nil
}

// DeleteTap delete tap device and related routing
func (tap *Tap) DeleteTap() error {
	link, err := netlink.LinkByName(tap.Link.Attrs().Name)
	if err != nil {
		return err
	}

	if err := netlink.LinkDel(link); err != nil {
		return fmt.Errorf("failed to delete %q: %v", link.Attrs().Name, err)
	}

	ipt, err := iptables.New()
	if err != nil {
		return err
	}

	// sudo iptables -t nat -I POSTROUTING 1 -o vethee574a50 -j MASQUERADE --source 192.168.0.3/24
	err = ipt.DeleteIfExists("nat", "POSTROUTING", "-o", tap.externalLinkName, "-j", "MASQUERADE", "--source", tap.Ip.String())
	if err != nil {
		return err
	}

	// sudo iptables -I FORWARD 1 -o tap0 -i vethee574a50 -j ACCEPT
	err = ipt.DeleteIfExists("filter", "FORWARD", "-o", tap.Link.Attrs().Name, "-i", tap.externalLinkName, "-j", "ACCEPT")
	if err != nil {
		return err
	}

	// sudo iptables -I FORWARD 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
	err = ipt.DeleteIfExists("filter", "FORWARD", "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT")
	if err != nil {
		return err
	}

	return nil
}