// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package main

import (
	"context"
	"net/netip"
	"testing"

	"tailscale.com/types/key"
)

func TestManagedNaviApplyNodeConfig(t *testing.T) {
	m := newManagedNavi(context.Background(), key.NewMachine(), t.Logf, "http://control.example.test", "navi-1")
	m.info = managedNaviNode{
		HostName: "navi.example.test",
		DERPPort: 8443,
		STUNPort: 3479,
	}

	hostname := "old.example.test"
	addr := "127.0.0.1:443"
	stunPort := 3478
	runDERP := true
	runSTUN := true

	m.ApplyNodeConfig(&hostname, &addr, &stunPort, &runDERP, &runSTUN)

	if hostname != "navi.example.test" {
		t.Fatalf("hostname = %q, want %q", hostname, "navi.example.test")
	}
	if addr != "127.0.0.1:8443" {
		t.Fatalf("addr = %q, want %q", addr, "127.0.0.1:8443")
	}
	if stunPort != 3479 {
		t.Fatalf("stunPort = %d, want %d", stunPort, 3479)
	}
	if !runDERP || !runSTUN {
		t.Fatalf("runDERP/runSTUN unexpectedly disabled: %v %v", runDERP, runSTUN)
	}
}

func TestManagedNaviApplyNodesChangeMismatchPullsFullList(t *testing.T) {
	m := newManagedNavi(context.Background(), key.NewMachine(), t.Logf, "http://control.example.test", "navi-1")
	want := key.NewNode().Public()
	pulled := false
	m.pullNodesFn = func() error {
		pulled = true
		return m.setTrustedNodes([]string{want.UntypedHexString()})
	}

	if err := m.applyNodesChange(managedNaviNodesChange{SeqNum: 3}); err != nil {
		t.Fatalf("applyNodesChange mismatch: %v", err)
	}
	if !pulled {
		t.Fatal("expected full trust list pull on sequence mismatch")
	}
	if err := m.VerifyClient(context.Background(), want, nil, netip.Addr{}); err != nil {
		t.Fatalf("trusted node was not installed after pull: %v", err)
	}
}

func TestManagedNaviApplyNodesChangeAddRemove(t *testing.T) {
	m := newManagedNavi(context.Background(), key.NewMachine(), t.Logf, "http://control.example.test", "navi-1")
	added := key.NewNode().Public()
	removed := key.NewNode().Public()
	if err := m.setTrustedNodes([]string{removed.UntypedHexString()}); err != nil {
		t.Fatalf("setTrustedNodes: %v", err)
	}

	if err := m.applyNodesChange(managedNaviNodesChange{
		SeqNum:     1,
		AddNode:    added.UntypedHexString(),
		RemoveNode: removed.UntypedHexString(),
	}); err != nil {
		t.Fatalf("applyNodesChange: %v", err)
	}

	if err := m.VerifyClient(context.Background(), added, nil, netip.Addr{}); err != nil {
		t.Fatalf("added node should be trusted: %v", err)
	}
	if err := m.VerifyClient(context.Background(), removed, nil, netip.Addr{}); err == nil {
		t.Fatal("removed node should no longer be trusted")
	}
}

func TestManagedNaviStoreFunnelPayloadNormalizesServiceCount(t *testing.T) {
	m := newManagedNavi(context.Background(), key.NewMachine(), t.Logf, "http://control.example.test", "navi-1")

	m.storeFunnelPayload(&managedNaviFunnelPayload{
		Edge: map[string]any{"edgeNodeId": "navi-1"},
		Services: []managedNaviFunnelSyncRecord{
			{OrgID: 1, Public: managedNaviFunnelPublic{Host: "example.test", ListenProto: "https", ListenPort: 443}},
		},
	})

	snapshot := m.funnelSnapshot()
	if snapshot.ServiceCount != 1 {
		t.Fatalf("ServiceCount = %d, want 1", snapshot.ServiceCount)
	}
	if len(snapshot.Services) != 1 {
		t.Fatalf("len(Services) = %d, want 1", len(snapshot.Services))
	}
	if got := snapshot.Edge["edgeNodeId"]; got != "navi-1" {
		t.Fatalf("edgeNodeId = %#v, want %q", got, "navi-1")
	}
}

func TestManagedNaviStoreFunnelPayloadHandlesNil(t *testing.T) {
	m := newManagedNavi(context.Background(), key.NewMachine(), t.Logf, "http://control.example.test", "navi-1")

	m.storeFunnelPayload(nil)

	snapshot := m.funnelSnapshot()
	if snapshot.ServiceCount != 0 {
		t.Fatalf("ServiceCount = %d, want 0", snapshot.ServiceCount)
	}
	if snapshot.Services == nil {
		t.Fatal("Services should be normalized to an empty slice")
	}
}
