package rule

import (
	"net/netip"
	"strings"

	"github.com/sagernet/sing-box/adapter"
	C "github.com/sagernet/sing-box/constant"
	"github.com/sagernet/sing-box/option"
	"github.com/sagernet/sing/common"
	"github.com/sagernet/sing/common/json/badjson"
	"github.com/sagernet/sing/common/json/badoption"
)

var _ RuleItem = (*NetworkInterfaceAddressItem)(nil)

type NetworkInterfaceAddressItem struct {
	networkManager     adapter.NetworkManager
	interfaceAddresses map[C.InterfaceType][]netip.Prefix
	description        string
}

func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem {
	item := &NetworkInterfaceAddressItem{
		networkManager:     networkManager,
		interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()),
	}
	var entryDescriptions []string
	for _, entry := range interfaceAddresses.Entries() {
		prefixes := make([]netip.Prefix, 0, len(entry.Value))
		for _, prefixable := range entry.Value {
			prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
		}
		item.interfaceAddresses[entry.Key.Build()] = prefixes
		entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
	}
	item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
	return item
}

func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
	interfaces := r.networkManager.NetworkInterfaces()
	myInterfaces := r.networkManager.InterfaceMonitor().MyInterfaces()
match:
	for ifType, addresses := range r.interfaceAddresses {
		for _, networkInterface := range interfaces {
			if common.Contains(myInterfaces, networkInterface.Name) {
				continue
			}
			if networkInterface.Type != ifType {
				continue
			}
			if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool {
				return common.Any(addresses, func(prefix netip.Prefix) bool {
					return prefix.Overlaps(it)
				})
			}) {
				continue match
			}
		}
		return false
	}
	return true
}

func (r *NetworkInterfaceAddressItem) String() string {
	return r.description
}
