//go:build linux

package main

// inspired by https://github.com/firecracker-microvm/firectl/blob/main/main.go#L64

import (
	"antoine-roux.tk/projects/go/firecracker-netns/internal/netlink"
	"antoine-roux.tk/projects/go/firecracker-netns/internal/netns"
	"context"
	"fmt"
	"github.com/firecracker-microvm/firecracker-go-sdk"
	"github.com/firecracker-microvm/firecracker-go-sdk/client/models"
	"github.com/sirupsen/logrus"
	"net"
	"os"
	"runtime"
)

func setupEnv() int {
	log := logrus.New()
	log.SetLevel(logrus.DebugLevel)

	newNs, err := netns.New()
	if err != nil {
		fmt.Println("new ns error", err)
		return 1
	}

	defer func(handle netns.NsHandle) {
		err := handle.Close()
		if err != nil {
			fmt.Println("close ns error", err)
		}
	}(newNs)

	defer func(ns netns.NsHandle) {
		err := netns.Delete(ns)
		if err != nil {
			fmt.Println("delete ns error", err)
		}
	}(newNs)

	vethPair, err := netlink.NewVirtualPairing(newNs, "wlp3s0")
	if err != nil {
		fmt.Println("new Veth error", err)
		return 1
	}

	defer func(veth *netlink.PairLink) {
		err = veth.DeleteVirtualPairing()
		if err != nil {
			fmt.Println("delete vethPair error", err)
		}
	}(vethPair)

	err = netns.Set(newNs)
	if err != nil {
		fmt.Println("set guest ns error", err)
		return 1
	}

	firstIpTapNetwork := net.IPv4(172, 16, 0, 1)
	tapNetwork := net.IPNet{
		IP:   firstIpTapNetwork,
		Mask: net.CIDRMask(30, 32),
	}

	tap, err := netlink.CreateTap(tapNetwork, vethPair.Guest.Link.Attrs().Name)
	if err != nil {
		fmt.Println("create tap in guest ns error", err)
		return 1
	}
	defer func(tap *netlink.Tap) {
		err := tap.DeleteTap()
		if err != nil {
			fmt.Println("delete tap error", err)
		}
	}(tap)

	// Do something with the network namespace
	interfaces, _ := net.Interfaces()
	log.Debugf("Interfaces: %v\n", interfaces)

	ctx := context.Background()
	cancel, cancelFunc := context.WithCancel(ctx)
	defer cancelFunc()

	cpuCount := int64(4)
	memorySize := int64(1024)
	isSmt := true
	socketPath := "/tmp/firecracker.socket"

	cfg := firecracker.Config{
		SocketPath:      socketPath,
		KernelImagePath: "./out/vmlinux-5.10.204",
		LogPath:         "./out/firecracker.log",
		LogLevel:        "Debug",
		KernelArgs:      "console=ttyS0 reboot=k panic=1 pci=off",
		Drives: []models.Drive{
			{
				DriveID:      firecracker.String("rootfs"),
				PathOnHost:   firecracker.String("./out/rootfs.ext4"),
				IsReadOnly:   firecracker.Bool(false),
				IsRootDevice: firecracker.Bool(true),
			},
		},
		NetworkInterfaces: firecracker.NetworkInterfaces{
			firecracker.NetworkInterface{
				StaticConfiguration: &firecracker.StaticNetworkConfiguration{
					MacAddress:  "06:00:AC:10:00:02",
					HostDevName: tap.Link.Attrs().Name,
					/*					IPConfiguration: &firecracker.IPConfiguration{
										IPAddr:      tapNetwork,
										Gateway:     firstIpTapNetwork,
										Nameservers: []string{"1.1.1.1"},
										IfName:      "net1",
									},*/
				},
			},
		},
		MachineCfg: models.MachineConfiguration{
			VcpuCount:       &cpuCount,
			MemSizeMib:      &memorySize,
			Smt:             &isSmt,
			TrackDirtyPages: true,
		},
	}

	firecrackerOpts := []firecracker.Opt{
		firecracker.WithProcessRunner(
			firecracker.VMCommandBuilder{}.
				WithBin("firecracker").
				WithSocketPath(socketPath).
				//WithStdin(os.Stdin).
				//WithStdout(os.Stdout).
				//WithStderr(os.Stderr).
				Build(ctx),
		),
		firecracker.WithLogger(logrus.NewEntry(log)),
	}

	vm, err := firecracker.NewMachine(cancel, cfg, firecrackerOpts...)
	if err != nil {
		log.Errorln("create vm error", err)
		return 1
	}
	defer os.Remove(cfg.SocketPath)

	defer vm.StopVMM()

	if err := vm.Start(ctx); err != nil {
		log.Errorln("start vm error", err)
		return 1
	}

	if err := vm.Wait(ctx); err != nil {
		log.Errorln("wait vm error", err)
		return 1
	}

	/*	cmd := exec.Command("/bin/sh")

		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		cmd.Env = []string{"PS1=-[ns-process]- # "}

		if err := cmd.Run(); err != nil {
			fmt.Printf("Error running the /bin/sh command - %s\n", err)
			os.Exit(1)
		}*/

	return 0
}

func main() {
	// Lock the OS Thread, so we don't accidentally switch namespaces
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	os.Exit(setupEnv())
}