package libbox

import (
	"net"
	"net/netip"

	"github.com/sagernet/sing-box/option"
	"github.com/sagernet/sing-tun"
	"github.com/sagernet/sing/common"
	E "github.com/sagernet/sing/common/exceptions"
)

type TunOptions interface {
	GetInet4Address() RoutePrefixIterator
	GetInet6Address() RoutePrefixIterator
	GetDNSServerAddress() (*StringBox, error)
	GetMTU() int32
	GetAutoRoute() bool
	GetStrictRoute() bool
	GetInet4RouteAddress() RoutePrefixIterator
	GetInet6RouteAddress() RoutePrefixIterator
	GetInet4RouteExcludeAddress() RoutePrefixIterator
	GetInet6RouteExcludeAddress() RoutePrefixIterator
	GetInet4RouteRange() RoutePrefixIterator
	GetInet6RouteRange() RoutePrefixIterator
	GetIncludePackage() StringIterator
	GetExcludePackage() StringIterator
	IsHTTPProxyEnabled() bool
	GetHTTPProxyServer() string
	GetHTTPProxyServerPort() int32
	GetHTTPProxyBypassDomain() StringIterator
	GetHTTPProxyMatchDomain() StringIterator
}

type RoutePrefix struct {
	address netip.Addr
	prefix  int
}

func (p *RoutePrefix) Address() string {
	return p.address.String()
}

func (p *RoutePrefix) Prefix() int32 {
	return int32(p.prefix)
}

func (p *RoutePrefix) Mask() string {
	var bits int
	if p.address.Is6() {
		bits = 128
	} else {
		bits = 32
	}
	return net.IP(net.CIDRMask(p.prefix, bits)).String()
}

func (p *RoutePrefix) String() string {
	return netip.PrefixFrom(p.address, p.prefix).String()
}

type RoutePrefixIterator interface {
	Next() *RoutePrefix
	HasNext() bool
}

func mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {
	return newIterator(common.Map(prefixes, func(prefix netip.Prefix) *RoutePrefix {
		return &RoutePrefix{
			address: prefix.Addr(),
			prefix:  prefix.Bits(),
		}
	}))
}

var _ TunOptions = (*tunOptions)(nil)

type tunOptions struct {
	*tun.Options
	routeRanges []netip.Prefix
	option.TunPlatformOptions
}

func (o *tunOptions) GetInet4Address() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet4Address)
}

func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet6Address)
}

func (o *tunOptions) GetDNSServerAddress() (*StringBox, error) {
	if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
		return nil, E.New("need one more IPv4 address for DNS hijacking")
	}
	return wrapString(o.Inet4Address[0].Addr().Next().String()), nil
}

func (o *tunOptions) GetMTU() int32 {
	return int32(o.MTU)
}

func (o *tunOptions) GetAutoRoute() bool {
	return o.AutoRoute
}

func (o *tunOptions) GetStrictRoute() bool {
	return o.StrictRoute
}

func (o *tunOptions) GetInet4RouteAddress() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet4RouteAddress)
}

func (o *tunOptions) GetInet6RouteAddress() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet6RouteAddress)
}

func (o *tunOptions) GetInet4RouteExcludeAddress() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet4RouteExcludeAddress)
}

func (o *tunOptions) GetInet6RouteExcludeAddress() RoutePrefixIterator {
	return mapRoutePrefix(o.Inet6RouteExcludeAddress)
}

func (o *tunOptions) GetInet4RouteRange() RoutePrefixIterator {
	return mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {
		return it.Addr().Is4()
	}))
}

func (o *tunOptions) GetInet6RouteRange() RoutePrefixIterator {
	return mapRoutePrefix(common.Filter(o.routeRanges, func(it netip.Prefix) bool {
		return it.Addr().Is6()
	}))
}

func (o *tunOptions) GetIncludePackage() StringIterator {
	return newIterator(o.IncludePackage)
}

func (o *tunOptions) GetExcludePackage() StringIterator {
	return newIterator(o.ExcludePackage)
}

func (o *tunOptions) IsHTTPProxyEnabled() bool {
	if o.TunPlatformOptions.HTTPProxy == nil {
		return false
	}
	return o.TunPlatformOptions.HTTPProxy.Enabled
}

func (o *tunOptions) GetHTTPProxyServer() string {
	return o.TunPlatformOptions.HTTPProxy.Server
}

func (o *tunOptions) GetHTTPProxyServerPort() int32 {
	return int32(o.TunPlatformOptions.HTTPProxy.ServerPort)
}

func (o *tunOptions) GetHTTPProxyBypassDomain() StringIterator {
	return newIterator(o.TunPlatformOptions.HTTPProxy.BypassDomain)
}

func (o *tunOptions) GetHTTPProxyMatchDomain() StringIterator {
	return newIterator(o.TunPlatformOptions.HTTPProxy.MatchDomain)
}