package peer

import (
	"fmt"
	"io"
	"net"
	"os"
	"syscall"

	"golang.org/x/sys/unix"
)

func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) {
	if len(network) != 4 {
		return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network))
	}
	ip := net.IPv4(network[0], network[1], network[2], localIP)

	//////////////////////////
	// Create TUN Interface //
	//////////////////////////

	tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600)
	if err != nil {
		return nil, fmt.Errorf("failed to open TUN device: %w", err)
	}

	// New interface request.
	req, err := unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create new TUN interface request: %w", err)
	}

	// Flags:
	//
	// IFF_NO_PI => don't add packet info data to packets sent to the interface.
	// IFF_TUN   => create a TUN device handling IP packets.
	req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN)

	err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req)
	if err != nil {
		return nil, fmt.Errorf("failed to set TUN device settings: %w", err)
	}

	// Name may not be exactly the same?
	name = req.Name()

	/////////////
	// Set MTU //
	/////////////

	// We need a socket file descriptor to set other options for some reason.
	sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
	if err != nil {
		return nil, fmt.Errorf("failed to open socket: %w", err)
	}
	defer unix.Close(sockFD)

	req, err = unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create MTU interface request: %w", err)
	}

	req.SetUint32(if_mtu)
	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil {
		return nil, fmt.Errorf("failed to set interface MTU: %w", err)
	}

	//////////////////////
	// Set Queue Length //
	//////////////////////

	req, err = unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create IP interface request: %w", err)
	}

	req.SetUint16(if_queue_len)
	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil {
		return nil, fmt.Errorf("failed to set interface queue length: %w", err)
	}

	/////////////////////
	// Set IP and Mask //
	/////////////////////

	req, err = unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create IP interface request: %w", err)
	}

	if err := req.SetInet4Addr(ip.To4()); err != nil {
		return nil, fmt.Errorf("failed to set interface request IP: %w", err)
	}

	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil {
		return nil, fmt.Errorf("failed to set interface IP: %w", err)
	}

	// SET MASK - must happen after setting address.
	req, err = unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create mask interface request: %w", err)
	}

	if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil {
		return nil, fmt.Errorf("failed to set interface request mask: %w", err)
	}

	if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil {
		return nil, fmt.Errorf("failed to set interface mask: %w", err)
	}

	////////////////////////
	// Bring Interface Up //
	////////////////////////

	req, err = unix.NewIfreq(name)
	if err != nil {
		return nil, fmt.Errorf("failed to create up interface request: %w", err)
	}

	// Get current flags.
	if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil {
		return nil, fmt.Errorf("failed to get interface flags: %w", err)
	}

	flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING

	// Set UP flag / broadcast flags.
	req.SetUint16(flags)
	if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil {
		return nil, fmt.Errorf("failed to set interface up: %w", err)
	}

	return os.NewFile(uintptr(tunFD), "tun"), nil
}
