package netlink

import (
	"errors"
	"fmt"

	"github.com/jsimonetti/rtnetlink"
)

type DeviceType uint16

type Link struct {
	Index       uint32
	Name        string
	DeviceType  DeviceType
	VirtualType string
	MTU         uint32
}

func (n *NetLink) LinkList() (links []Link, err error) {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return nil, fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	linkMessages, err := conn.Link.List()
	if err != nil {
		return nil, fmt.Errorf("listing interfaces: %w", err)
	}

	links = make([]Link, len(linkMessages))
	for i, message := range linkMessages {
		virtualType := ""
		if message.Attributes.Info != nil {
			virtualType = message.Attributes.Info.Kind
		}
		links[i] = Link{
			Index:       message.Index,
			Name:        message.Attributes.Name,
			DeviceType:  DeviceType(message.Type),
			VirtualType: virtualType,
			MTU:         message.Attributes.MTU,
		}
	}

	return links, nil
}

var ErrLinkNotFound = errors.New("link not found")

func (n *NetLink) LinkByName(name string) (link Link, err error) {
	links, err := n.LinkList()
	if err != nil {
		return Link{}, fmt.Errorf("listing links: %w", err)
	}

	for _, link := range links {
		if link.Name == name {
			return link, nil
		}
	}

	return Link{}, fmt.Errorf("%w: for name %s", ErrLinkNotFound, name)
}

func (n *NetLink) LinkByIndex(index uint32) (link Link, err error) {
	links, err := n.LinkList()
	if err != nil {
		return Link{}, fmt.Errorf("listing links: %w", err)
	}

	for _, link = range links {
		if link.Index == index {
			return link, nil
		}
	}

	return Link{}, fmt.Errorf("%w: for index %d", ErrLinkNotFound, index)
}

func (n *NetLink) LinkAdd(link Link) (linkIndex uint32, err error) {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return 0, fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	tx := &rtnetlink.LinkMessage{
		Type: uint16(link.DeviceType),
		Attributes: &rtnetlink.LinkAttributes{
			MTU:  link.MTU,
			Name: link.Name,
		},
	}
	if link.VirtualType != "" {
		tx.Attributes.Info = &rtnetlink.LinkInfo{
			Kind: link.VirtualType,
		}
	}

	err = conn.Link.New(tx)
	if err != nil {
		return 0, fmt.Errorf("creating new link: %w", err)
	}

	linkMessages, err := conn.Link.List()
	if err != nil {
		return 0, fmt.Errorf("listing links: %w", err)
	}
	for _, linkMessage := range linkMessages {
		if linkMessage.Attributes.Name == link.Name {
			return linkMessage.Index, nil
		}
	}

	return 0, fmt.Errorf("%w: matching name %s", ErrLinkNotFound, link.Name)
}

func (n *NetLink) LinkDel(linkIndex uint32) (err error) {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	return conn.Link.Delete(linkIndex)
}

func (n *NetLink) LinkSetUp(linkIndex uint32) (err error) {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	rx, err := conn.Link.Get(linkIndex)
	if err != nil {
		return fmt.Errorf("getting link: %w", err)
	}
	tx := &rtnetlink.LinkMessage{
		Type:   rx.Type,
		Index:  linkIndex,
		Flags:  iffUp,
		Change: iffUp,
	}
	return conn.Link.Set(tx)
}

func (n *NetLink) LinkSetDown(linkIndex uint32) (err error) {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	linkInfo, err := conn.Link.Get(linkIndex)
	if err != nil {
		return fmt.Errorf("getting link: %w", err)
	}
	message := &rtnetlink.LinkMessage{
		Type:   linkInfo.Type,
		Index:  linkIndex,
		Flags:  0,
		Change: iffUp,
	}
	return conn.Link.Set(message)
}

func (n *NetLink) LinkSetMTU(linkIndex, mtu uint32) error {
	conn, err := rtnetlink.Dial(nil)
	if err != nil {
		return fmt.Errorf("dialing netlink: %w", err)
	}
	defer conn.Close()

	message := &rtnetlink.LinkMessage{
		Index: linkIndex,
		Attributes: &rtnetlink.LinkAttributes{
			MTU: mtu,
		},
	}

	err = conn.Link.Set(message)
	if err != nil {
		return fmt.Errorf("setting MTU to %d for link at index %d: %w",
			mtu, linkIndex, err)
	}

	return nil
}
