//go:build android

package notifier

import (
	"net/netip"
	"slices"
	"sort"
	"strings"
	"sync"

	"github.com/netbirdio/netbird/client/internal/listener"
	"github.com/netbirdio/netbird/route"
)

type Notifier struct {
	initialRoutes []*route.Route
	currentRoutes []*route.Route
	fakeIPRoutes []*route.Route

	listener    listener.NetworkChangeListener
	listenerMux sync.Mutex
}

func NewNotifier() *Notifier {
	return &Notifier{}
}

func (n *Notifier) SetListener(listener listener.NetworkChangeListener) {
	n.listenerMux.Lock()
	defer n.listenerMux.Unlock()
	n.listener = listener
}

// SetInitialClientRoutes stores the initial route sets for TUN configuration.
func (n *Notifier) SetInitialClientRoutes(initialRoutes []*route.Route, routesForComparison []*route.Route) {
	n.initialRoutes = filterStatic(initialRoutes)
	n.currentRoutes = filterStatic(routesForComparison)
}

// SetFakeIPRoutes stores the fake IP routes to be included in every TUN rebuild.
func (n *Notifier) SetFakeIPRoutes(routes []*route.Route) {
	n.fakeIPRoutes = routes
}

func (n *Notifier) OnNewRoutes(idMap route.HAMap) {
	var newRoutes []*route.Route
	for _, routes := range idMap {
		for _, r := range routes {
			if r.IsDynamic() {
				continue
			}
			newRoutes = append(newRoutes, r)
		}
	}

	if !n.hasRouteDiff(n.currentRoutes, newRoutes) {
		return
	}

	n.currentRoutes = newRoutes
	n.notify()
}

func (n *Notifier) OnNewPrefixes([]netip.Prefix) {
	// Not used on Android
}

func (n *Notifier) notify() {
	n.listenerMux.Lock()
	defer n.listenerMux.Unlock()
	if n.listener == nil {
		return
	}

	allRoutes := slices.Clone(n.currentRoutes)
	allRoutes = append(allRoutes, n.fakeIPRoutes...)

	routeStrings := n.routesToStrings(allRoutes)
	sort.Strings(routeStrings)
	go func(l listener.NetworkChangeListener) {
		l.OnNetworkChanged(strings.Join(routeStrings, ","))
	}(n.listener)
}

func filterStatic(routes []*route.Route) []*route.Route {
	out := make([]*route.Route, 0, len(routes))
	for _, r := range routes {
		if !r.IsDynamic() {
			out = append(out, r)
		}
	}
	return out
}

func (n *Notifier) routesToStrings(routes []*route.Route) []string {
	nets := make([]string, 0, len(routes))
	for _, r := range routes {
		nets = append(nets, r.NetString())
	}
	return nets
}

func (n *Notifier) hasRouteDiff(a []*route.Route, b []*route.Route) bool {
	slices.SortFunc(a, func(x, y *route.Route) int {
		return strings.Compare(x.NetString(), y.NetString())
	})
	slices.SortFunc(b, func(x, y *route.Route) int {
		return strings.Compare(x.NetString(), y.NetString())
	})

	return !slices.EqualFunc(a, b, func(x, y *route.Route) bool {
		return x.NetString() == y.NetString()
	})
}

func (n *Notifier) GetInitialRouteRanges() []string {
	initialStrings := n.routesToStrings(n.initialRoutes)
	sort.Strings(initialStrings)
	return initialStrings
}
