package config

import (
	"net/netip"

	C "github.com/metacubex/mihomo/constant"

	"go4.org/netipx"
	"golang.org/x/exp/slices"
)

type Tun struct {
	Enable              bool       `yaml:"enable" json:"enable"`
	Device              string     `yaml:"device" json:"device"`
	Stack               C.TUNStack `yaml:"stack" json:"stack"`
	DNSHijack           []string   `yaml:"dns-hijack" json:"dns-hijack"`
	AutoRoute           bool       `yaml:"auto-route" json:"auto-route"`
	AutoDetectInterface bool       `yaml:"auto-detect-interface" json:"auto-detect-interface"`

	MTU                    uint32         `yaml:"mtu" json:"mtu,omitempty"`
	GSO                    bool           `yaml:"gso" json:"gso,omitempty"`
	GSOMaxSize             uint32         `yaml:"gso-max-size" json:"gso-max-size,omitempty"`
	Inet4Address           []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
	Inet6Address           []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
	IPRoute2TableIndex     int            `yaml:"iproute2-table-index" json:"iproute2-table-index,omitempty"`
	IPRoute2RuleIndex      int            `yaml:"iproute2-rule-index" json:"iproute2-rule-index,omitempty"`
	AutoRedirect           bool           `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
	AutoRedirectInputMark  uint32         `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
	AutoRedirectOutputMark uint32         `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
	LoopbackAddress        []netip.Addr   `yaml:"loopback-address" json:"loopback-address,omitempty"`
	StrictRoute            bool           `yaml:"strict-route" json:"strict-route,omitempty"`
	RouteAddress           []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
	RouteAddressSet        []string       `yaml:"route-address-set" json:"route-address-set,omitempty"`
	RouteExcludeAddress    []netip.Prefix `yaml:"route-exclude-address" json:"route-exclude-address,omitempty"`
	RouteExcludeAddressSet []string       `yaml:"route-exclude-address-set" json:"route-exclude-address-set,omitempty"`
	IncludeInterface       []string       `yaml:"include-interface" json:"include-interface,omitempty"`
	ExcludeInterface       []string       `yaml:"exclude-interface" json:"exclude-interface,omitempty"`
	IncludeUID             []uint32       `yaml:"include-uid" json:"include-uid,omitempty"`
	IncludeUIDRange        []string       `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
	ExcludeUID             []uint32       `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
	ExcludeUIDRange        []string       `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
	ExcludeSrcPort         []uint16       `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
	ExcludeSrcPortRange    []string       `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
	ExcludeDstPort         []uint16       `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
	ExcludeDstPortRange    []string       `yaml:"exclude-dst-port-range" json:"exclude-dst-port-range,omitempty"`
	IncludeAndroidUser     []int          `yaml:"include-android-user" json:"include-android-user,omitempty"`
	IncludePackage         []string       `yaml:"include-package" json:"include-package,omitempty"`
	ExcludePackage         []string       `yaml:"exclude-package" json:"exclude-package,omitempty"`
	EndpointIndependentNat bool           `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
	UDPTimeout             int64          `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
	DisableICMPForwarding  bool           `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,omitempty"`
	FileDescriptor         int            `yaml:"file-descriptor" json:"file-descriptor"`

	Inet4RouteAddress        []netip.Prefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
	Inet6RouteAddress        []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
	Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
	Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`

	// darwin special config
	RecvMsgX bool `yaml:"recvmsgx" json:"recvmsgx,omitempty"`
	SendMsgX bool `yaml:"sendmsgx" json:"sendmsgx,omitempty"`
}

func (t *Tun) Sort() {
	slices.Sort(t.DNSHijack)

	slices.SortFunc(t.Inet4Address, netipx.ComparePrefix)
	slices.SortFunc(t.Inet6Address, netipx.ComparePrefix)
	slices.SortFunc(t.RouteAddress, netipx.ComparePrefix)
	slices.Sort(t.RouteAddressSet)
	slices.SortFunc(t.RouteExcludeAddress, netipx.ComparePrefix)
	slices.Sort(t.RouteExcludeAddressSet)
	slices.Sort(t.IncludeInterface)
	slices.Sort(t.ExcludeInterface)
	slices.Sort(t.IncludeUID)
	slices.Sort(t.IncludeUIDRange)
	slices.Sort(t.ExcludeUID)
	slices.Sort(t.ExcludeUIDRange)
	slices.Sort(t.IncludeAndroidUser)
	slices.Sort(t.IncludePackage)
	slices.Sort(t.ExcludePackage)

	slices.SortFunc(t.Inet4RouteAddress, netipx.ComparePrefix)
	slices.SortFunc(t.Inet6RouteAddress, netipx.ComparePrefix)
	slices.SortFunc(t.Inet4RouteExcludeAddress, netipx.ComparePrefix)
	slices.SortFunc(t.Inet6RouteExcludeAddress, netipx.ComparePrefix)
}

func (t *Tun) Equal(other Tun) bool {
	if t.Enable != other.Enable {
		return false
	}
	if t.Device != other.Device {
		return false
	}
	if t.Stack != other.Stack {
		return false
	}
	if !slices.Equal(t.DNSHijack, other.DNSHijack) {
		return false
	}
	if t.AutoRoute != other.AutoRoute {
		return false
	}
	if t.AutoDetectInterface != other.AutoDetectInterface {
		return false
	}

	if t.MTU != other.MTU {
		return false
	}
	if t.GSO != other.GSO {
		return false
	}
	if t.GSOMaxSize != other.GSOMaxSize {
		return false
	}
	if !slices.Equal(t.Inet4Address, other.Inet4Address) {
		return false
	}
	if !slices.Equal(t.Inet6Address, other.Inet6Address) {
		return false
	}
	if t.IPRoute2TableIndex != other.IPRoute2TableIndex {
		return false
	}
	if t.IPRoute2RuleIndex != other.IPRoute2RuleIndex {
		return false
	}
	if t.AutoRedirect != other.AutoRedirect {
		return false
	}
	if t.AutoRedirectInputMark != other.AutoRedirectInputMark {
		return false
	}
	if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
		return false
	}
	if !slices.Equal(t.RouteAddress, other.RouteAddress) {
		return false
	}
	if t.StrictRoute != other.StrictRoute {
		return false
	}
	if !slices.Equal(t.RouteAddress, other.RouteAddress) {
		return false
	}
	if !slices.Equal(t.RouteAddressSet, other.RouteAddressSet) {
		return false
	}
	if !slices.Equal(t.RouteExcludeAddress, other.RouteExcludeAddress) {
		return false
	}
	if !slices.Equal(t.RouteExcludeAddressSet, other.RouteExcludeAddressSet) {
		return false
	}
	if !slices.Equal(t.IncludeInterface, other.IncludeInterface) {
		return false
	}
	if !slices.Equal(t.ExcludeInterface, other.ExcludeInterface) {
		return false
	}
	if !slices.Equal(t.IncludeUID, other.IncludeUID) {
		return false
	}
	if !slices.Equal(t.IncludeUIDRange, other.IncludeUIDRange) {
		return false
	}
	if !slices.Equal(t.ExcludeUID, other.ExcludeUID) {
		return false
	}
	if !slices.Equal(t.ExcludeUIDRange, other.ExcludeUIDRange) {
		return false
	}
	if !slices.Equal(t.IncludeAndroidUser, other.IncludeAndroidUser) {
		return false
	}
	if !slices.Equal(t.IncludePackage, other.IncludePackage) {
		return false
	}
	if !slices.Equal(t.ExcludePackage, other.ExcludePackage) {
		return false
	}
	if t.EndpointIndependentNat != other.EndpointIndependentNat {
		return false
	}
	if t.UDPTimeout != other.UDPTimeout {
		return false
	}
	if t.DisableICMPForwarding != other.DisableICMPForwarding {
		return false
	}
	if t.FileDescriptor != other.FileDescriptor {
		return false
	}

	if !slices.Equal(t.Inet4RouteAddress, other.Inet4RouteAddress) {
		return false
	}
	if !slices.Equal(t.Inet6RouteAddress, other.Inet6RouteAddress) {
		return false
	}
	if !slices.Equal(t.Inet4RouteExcludeAddress, other.Inet4RouteExcludeAddress) {
		return false
	}
	if !slices.Equal(t.Inet6RouteExcludeAddress, other.Inet6RouteExcludeAddress) {
		return false
	}

	if t.RecvMsgX != other.RecvMsgX {
		return false
	}
	if t.SendMsgX != other.SendMsgX {
		return false
	}

	return true
}
