package client

import (
	"context"
	"fmt"
	"net/netip"
	"sync"
	"testing"
	"time"

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

type benchmarkTier struct {
	name            string
	peers           int
	routes          int
	haPeersPerGroup int
}

var benchmarkTiers = []benchmarkTier{
	{"Small", 100, 50, 4},
	{"Medium", 1000, 200, 16},
	{"Large", 5000, 500, 32},
}

type mockRouteHandler struct {
	network string
}

func (m *mockRouteHandler) String() string                 { return m.network }
func (m *mockRouteHandler) AddRoute(context.Context) error { return nil }
func (m *mockRouteHandler) RemoveRoute() error             { return nil }
func (m *mockRouteHandler) AddAllowedIPs(string) error     { return nil }
func (m *mockRouteHandler) RemoveAllowedIPs() error        { return nil }

func generateBenchmarkData(tier benchmarkTier) (*peer.Status, map[route.ID]*route.Route) {
	statusRecorder := peer.NewRecorder("test-mgm")
	routes := make(map[route.ID]*route.Route)

	peerKeys := make([]string, tier.peers)
	for i := 0; i < tier.peers; i++ {
		peerKey := fmt.Sprintf("peer-%d", i)
		peerKeys[i] = peerKey
		fqdn := fmt.Sprintf("peer-%d.example.com", i)
		ip := fmt.Sprintf("10.0.%d.%d", i/256, i%256)

		err := statusRecorder.AddPeer(peerKey, fqdn, ip, "")
		if err != nil {
			panic(fmt.Sprintf("failed to add peer: %v", err))
		}

		var status peer.ConnStatus
		var latency time.Duration
		relayed := false

		switch i % 10 {
		case 0, 1: // 20% disconnected
			status = peer.StatusConnecting
			latency = 0
		case 2: // 10% idle
			status = peer.StatusIdle
			latency = 50 * time.Millisecond
		case 3, 4: // 20% relayed
			status = peer.StatusConnected
			relayed = true
			latency = time.Duration(50+i%100) * time.Millisecond
		default: // 50% direct connection
			status = peer.StatusConnected
			latency = time.Duration(10+i%40) * time.Millisecond
		}

		// Update peer state
		state := peer.State{
			PubKey:           peerKey,
			IP:               ip,
			FQDN:             fqdn,
			ConnStatus:       status,
			ConnStatusUpdate: time.Now(),
			Relayed:          relayed,
			Latency:          latency,
			Mux:              &sync.RWMutex{},
		}

		err = statusRecorder.UpdatePeerState(state)
		if err != nil {
			panic(fmt.Sprintf("failed to update peer state: %v", err))
		}
	}

	routeID := 0
	for i := 0; i < tier.routes; i++ {
		network := fmt.Sprintf("192.168.%d.0/24", i%256)
		prefix := netip.MustParsePrefix(network)

		haGroupSize := 1
		if i%4 == 0 { // 25% of routes have HA
			haGroupSize = tier.haPeersPerGroup
		}

		for j := 0; j < haGroupSize; j++ {
			peerIndex := (i*tier.haPeersPerGroup + j) % tier.peers
			peerKey := peerKeys[peerIndex]

			rID := route.ID(fmt.Sprintf("route-%d-%d", i, j))

			metric := 100 + j*10

			routes[rID] = &route.Route{
				ID:      rID,
				Network: prefix,
				Peer:    peerKey,
				Metric:  metric,
				NetID:   route.NetID(fmt.Sprintf("net-%d", i)),
			}
			routeID++
		}
	}

	return statusRecorder, routes
}

// Benchmark the optimized recalculate routes
func BenchmarkRecalculateRoutes(b *testing.B) {
	for _, tier := range benchmarkTiers {
		b.Run(tier.name, func(b *testing.B) {
			statusRecorder, routes := generateBenchmarkData(tier)

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()

			watcher := &Watcher{
				ctx:                 ctx,
				statusRecorder:      statusRecorder,
				routes:              routes,
				routePeersNotifiers: make(map[string]chan struct{}),
				routeUpdate:         make(chan RoutesUpdate),
				peerStateUpdate:     make(chan map[string]peer.RouterState),
				handler:             &mockRouteHandler{network: "benchmark"},
				currentChosenStatus: nil,
			}

			b.ResetTimer()
			b.ReportAllocs()

			routePeerStatuses := watcher.getRouterPeerStatuses()
			for i := 0; i < b.N; i++ {
				err := watcher.recalculateRoutes(reasonPeerUpdate, routePeerStatuses)
				if err != nil {
					b.Fatalf("recalculateRoutes failed: %v", err)
				}
			}
		})
	}
}
