package router

import (
	"net/netip"
	"reflect"
	"slices"
)

// Router is responsible for managing the system network stack.
type Router interface {
	// Set updates the OS network stack with a new Config. It may be
	// called multiple times with identical Configs, which the
	// implementation should handle gracefully. If it is a full tunnel config, kill switch is enabled
	// for the duration of the tunnel already independently enabled.
	Set(*Config) error

	// Close closes the router, cleaning up routes and disabling kill switch if it was enabled by the router.
	Close() error

	// GetPhysicalInterfaceIndex only relevant for windows, returns the index of the default physical interface
	GetPhysicalInterfaceIndex() uint32
}

// Config is the subset of configuration that is relevant to our Router
type Config struct {
	// TunnelAddrs are the addresses for the tunnel interface
	TunnelAddrs []netip.Prefix

	// DNS configured for the tunnel, falls back to system if not set
	DNS []netip.Addr

	SearchDomains []string

	// Routes are the routes that point into the tunnel
	// interface.  These are the /32 and /128 routes to peers, (AllowedIps).
	Routes []netip.Prefix

	// Falls back to WG default if not set
	MTU int

	// PeerEndpoints are /32 or /128 prefixes for every peer's public endpoint.
	PeerEndpoints []netip.Prefix

	// Generated by system if not set
	ListenPort uint16
}

func (c *Config) Equal(b *Config) bool {
	if c == nil && b == nil {
		return true
	}
	if (c == nil) != (b == nil) {
		return false
	}
	return reflect.DeepEqual(c, b)
}

func (c *Config) Clone() *Config {
	if c == nil {
		return nil
	}
	c2 := *c
	c2.TunnelAddrs = slices.Clone(c.TunnelAddrs)
	c2.DNS = slices.Clone(c.DNS)
	c2.Routes = slices.Clone(c.Routes)
	return &c2
}

// HasDefaultRoute checks if tunnel is full tunnel
func (c *Config) hasDefaultRoute(v4 bool) bool {
	if c == nil {
		return false
	}
	for _, rt := range c.Routes {
		if rt.Bits() == 0 && ((v4 && rt.Addr().Is4()) || (!v4 && rt.Addr().Is6())) {
			return true
		}
	}
	return false
}

func (c *Config) HasAnyDefaultRoute() bool {
	return c.hasDefaultRoute(true) || c.hasDefaultRoute(false)
}
