package internal

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

	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"

	"github.com/netbirdio/netbird/client/iface/wgaddr"
	"github.com/netbirdio/netbird/client/internal/lazyconn"
	"github.com/netbirdio/netbird/client/internal/peer"
	"github.com/netbirdio/netbird/client/internal/peerstore"
	"github.com/netbirdio/netbird/monotime"
)

func TestResolveLazyForce(t *testing.T) {
	tests := []struct {
		name   string
		env    string
		envSet bool
		mdm    lazyconn.State
		want   lazyForce
	}{
		{name: "env unset, mdm unset -> defer to management", mdm: lazyconn.StateUnset, want: lazyForceNone},
		{name: "env on -> force on", env: "on", envSet: true, mdm: lazyconn.StateUnset, want: lazyForceOn},
		{name: "env off -> force off", env: "off", envSet: true, mdm: lazyconn.StateUnset, want: lazyForceOff},
		{name: "env unset, mdm on -> force on", mdm: lazyconn.StateOn, want: lazyForceOn},
		{name: "env unset, mdm off -> force off", mdm: lazyconn.StateOff, want: lazyForceOff},
		{name: "env on beats mdm off", env: "on", envSet: true, mdm: lazyconn.StateOff, want: lazyForceOn},
		{name: "env off beats mdm on", env: "off", envSet: true, mdm: lazyconn.StateOn, want: lazyForceOff},
		{name: "unrecognized env, mdm on -> mdm wins", env: "auto", envSet: true, mdm: lazyconn.StateOn, want: lazyForceOn},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Setenv(lazyconn.EnvLazyConn, tt.env)
			if !tt.envSet {
				os.Unsetenv(lazyconn.EnvLazyConn)
			}

			if got := resolveLazyForce(tt.mdm); got != tt.want {
				t.Fatalf("resolveLazyForce(%v) = %v, want %v", tt.mdm, got, tt.want)
			}
		})
	}
}

type mockLazyWGIface struct{}

func (mockLazyWGIface) RemovePeer(string) error { return nil }
func (mockLazyWGIface) UpdatePeer(string, []netip.Prefix, time.Duration, *net.UDPAddr, *wgtypes.Key) error {
	return nil
}
func (mockLazyWGIface) IsUserspaceBind() bool                    { return false }
func (mockLazyWGIface) Address() wgaddr.Address                  { return wgaddr.Address{} }
func (mockLazyWGIface) LastActivities() map[string]monotime.Time { return nil }
func (mockLazyWGIface) MTU() uint16                              { return 1280 }

// TestConnMgr_ActivatePeerConcurrentWithLifecycle exercises ActivatePeer from
// non-engine goroutines (the DNS warm-up path) racing the manager lifecycle,
// which stays on the engine loop. Run with -race: it fails if ActivatePeer
// still requires engine.syncMsgMux for safety.
func TestConnMgr_ActivatePeerConcurrentWithLifecycle(t *testing.T) {
	t.Setenv(lazyconn.EnvLazyConn, "on")

	status := peer.NewRecorder("https://mgm")
	store := peerstore.NewConnStore()
	connMgr := NewConnMgr(&EngineConfig{}, status, store, mockLazyWGIface{})

	conn := newTestPeerConn(t, "peerA")

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

	done := make(chan struct{})
	var wg sync.WaitGroup
	for range 4 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for {
				select {
				case <-done:
					return
				default:
					connMgr.ActivatePeer(ctx, conn)
				}
			}
		}()
	}

	// Let the activators spin against the started manager, then tear it down
	// underneath them and let them spin against the stopped manager.
	time.Sleep(100 * time.Millisecond)
	connMgr.Close()
	time.Sleep(50 * time.Millisecond)

	close(done)
	wg.Wait()
}

func TestInactivityThresholdEnv(t *testing.T) {
	tests := []struct {
		name string
		val  string
		want *time.Duration
	}{
		{name: "unset", val: "", want: nil},
		{name: "go duration minutes", val: "30m", want: durPtr(30 * time.Minute)},
		{name: "go duration hours", val: "1h", want: durPtr(time.Hour)},
		{name: "go duration seconds", val: "90s", want: durPtr(90 * time.Second)},
		{name: "bare integer is minutes (backwards compat)", val: "5", want: durPtr(5 * time.Minute)},
		{name: "zero duration", val: "0s", want: nil},
		{name: "zero integer", val: "0", want: nil},
		{name: "negative duration", val: "-5m", want: nil},
		{name: "garbage", val: "abc", want: nil},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Setenv(lazyconn.EnvInactivityThreshold, tc.val)
			got := inactivityThresholdEnv()
			switch {
			case tc.want == nil && got != nil:
				t.Fatalf("want nil, got %v", *got)
			case tc.want != nil && got == nil:
				t.Fatalf("want %v, got nil", *tc.want)
			case tc.want != nil && *got != *tc.want:
				t.Fatalf("want %v, got %v", *tc.want, *got)
			}
		})
	}
}

func durPtr(d time.Duration) *time.Duration { return &d }
