package control

import (
	"net"
	"net/netip"
	_ "unsafe"

	"github.com/sagernet/sing/common"
	E "github.com/sagernet/sing/common/exceptions"
	M "github.com/sagernet/sing/common/metadata"
)

var _ InterfaceFinder = (*DefaultInterfaceFinder)(nil)

type DefaultInterfaceFinder struct {
	interfaces []Interface
}

func NewDefaultInterfaceFinder() *DefaultInterfaceFinder {
	return &DefaultInterfaceFinder{}
}

func (f *DefaultInterfaceFinder) Update() error {
	netIfs, err := net.Interfaces()
	if err != nil {
		return err
	}
	interfaces := make([]Interface, 0, len(netIfs))
	for _, netIf := range netIfs {
		ifAddrs, err := netIf.Addrs()
		if err != nil {
			return err
		}
		interfaces = append(interfaces, Interface{
			Index:        netIf.Index,
			MTU:          netIf.MTU,
			Name:         netIf.Name,
			Addresses:    common.Map(ifAddrs, M.PrefixFromNet),
			HardwareAddr: netIf.HardwareAddr,
			Flags:        netIf.Flags,
		})
	}
	f.interfaces = interfaces
	return nil
}

func (f *DefaultInterfaceFinder) UpdateInterfaces(interfaces []Interface) {
	f.interfaces = interfaces
}

func (f *DefaultInterfaceFinder) Interfaces() []Interface {
	return f.interfaces
}

func (f *DefaultInterfaceFinder) InterfaceIndexByName(name string) (int, error) {
	for _, netInterface := range f.interfaces {
		if netInterface.Name == name {
			return netInterface.Index, nil
		}
	}
	netInterface, err := net.InterfaceByName(name)
	if err != nil {
		return 0, err
	}
	f.Update()
	return netInterface.Index, nil
}

func (f *DefaultInterfaceFinder) InterfaceNameByIndex(index int) (string, error) {
	for _, netInterface := range f.interfaces {
		if netInterface.Index == index {
			return netInterface.Name, nil
		}
	}
	netInterface, err := net.InterfaceByIndex(index)
	if err != nil {
		return "", err
	}
	f.Update()
	return netInterface.Name, nil
}

func (f *DefaultInterfaceFinder) InterfaceByAddr(addr netip.Addr) (*Interface, error) {
	for _, netInterface := range f.interfaces {
		for _, prefix := range netInterface.Addresses {
			if prefix.Contains(addr) {
				return &netInterface, nil
			}
		}
	}
	err := f.Update()
	if err != nil {
		return nil, err
	}
	for _, netInterface := range f.interfaces {
		for _, prefix := range netInterface.Addresses {
			if prefix.Contains(addr) {
				return &netInterface, nil
			}
		}
	}
	return nil, &net.OpError{Op: "route", Net: "ip+net", Source: nil, Addr: &net.IPAddr{IP: addr.AsSlice()}, Err: E.New("no such network interface")}
}
