// Package tap provides Linux TAP (Layer 2) device management.
//
// A TAP device is a virtual Ethernet interface that allows userspace programs
// to read and write raw Ethernet frames. SoftEther VPN operates at Layer 2,
// so it bridges Ethernet frames between the TAP device and the VPN tunnel.
//
// This uses the Linux kernel's TUN/TAP driver via /dev/net/tun with the
// IFF_TAP | IFF_NO_PI flags (TAP mode, no packet info header).
//
// See: https://www.kernel.org/doc/html/latest/networking/tuntap.html
package tap

import (
	"fmt"
	"net"
	"os"
	"unsafe"

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

const (
	tunDevice  = "/dev/net/tun"
	ifnameSize = 16
)

// Device represents an open TAP network interface.
type Device struct {
	file *os.File
	Name string // Kernel-assigned interface name (e.g. "tap0")
}

// Open creates a new TAP device with the given name.
// If name is empty, the kernel assigns one automatically (tap0, tap1, ...).
// Requires CAP_NET_ADMIN or root privileges.
func Open(name string) (*Device, error) {
	fd, err := unix.Open(tunDevice, unix.O_RDWR|unix.O_CLOEXEC, 0)
	if err != nil {
		return nil, fmt.Errorf("open %s: %w", tunDevice, err)
	}

	var ifr [unix.IFNAMSIZ + 64]byte
	// IFF_TAP: Layer 2 (Ethernet frames), IFF_NO_PI: no 4-byte packet info header
	flags := uint16(unix.IFF_TAP | unix.IFF_NO_PI)
	ifr[ifnameSize] = byte(flags)
	ifr[ifnameSize+1] = byte(flags >> 8)

	if name != "" {
		copy(ifr[:ifnameSize], name)
	}

	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), unix.TUNSETIFF, uintptr(unsafe.Pointer(&ifr[0])))
	if errno != 0 {
		unix.Close(fd)
		return nil, fmt.Errorf("ioctl TUNSETIFF: %w", errno)
	}

	// Read back the kernel-assigned name
	assignedName := string(ifr[:ifnameSize])
	for i, b := range assignedName {
		if b == 0 {
			assignedName = assignedName[:i]
			break
		}
	}

	return &Device{
		file: os.NewFile(uintptr(fd), tunDevice),
		Name: assignedName,
	}, nil
}

// Read reads a single Ethernet frame from the TAP device.
func (d *Device) Read(buf []byte) (int, error) {
	return d.file.Read(buf)
}

// Write writes a single Ethernet frame to the TAP device.
func (d *Device) Write(buf []byte) (int, error) {
	return d.file.Write(buf)
}

// Close closes the TAP device file descriptor, which also removes the interface.
func (d *Device) Close() error {
	return d.file.Close()
}

// MAC returns the hardware (MAC) address of the TAP interface.
func (d *Device) MAC() (net.HardwareAddr, error) {
	sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
	if err != nil {
		return nil, fmt.Errorf("socket: %w", err)
	}
	defer unix.Close(sock)

	var ifr [40]byte
	copy(ifr[:ifnameSize], d.Name)

	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCGIFHWADDR, uintptr(unsafe.Pointer(&ifr[0])))
	if errno != 0 {
		return nil, fmt.Errorf("SIOCGIFHWADDR: %w", errno)
	}
	// MAC starts at offset ifnameSize+2 (sa_family is 2 bytes)
	mac := make(net.HardwareAddr, 6)
	copy(mac, ifr[ifnameSize+2:ifnameSize+8])
	return mac, nil
}

// SetUp brings the TAP interface up (equivalent to `ip link set <name> up`).
func (d *Device) SetUp() error {
	sock, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
	if err != nil {
		return fmt.Errorf("socket: %w", err)
	}
	defer unix.Close(sock)

	var ifr [40]byte
	copy(ifr[:ifnameSize], d.Name)

	// SIOCGIFFLAGS: get current interface flags
	_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifr[0])))
	if errno != 0 {
		return fmt.Errorf("SIOCGIFFLAGS: %w", errno)
	}

	// Set IFF_UP flag
	flags := uint16(ifr[ifnameSize]) | uint16(ifr[ifnameSize+1])<<8
	flags |= unix.IFF_UP
	ifr[ifnameSize] = byte(flags)
	ifr[ifnameSize+1] = byte(flags >> 8)

	// SIOCSIFFLAGS: apply updated flags
	_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(sock), unix.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifr[0])))
	if errno != 0 {
		return fmt.Errorf("SIOCSIFFLAGS: %w", errno)
	}

	return nil
}
