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

package integration

import (
	"errors"
	"testing"
	"time"

	"tailscale.com/tailcfg"
	"tailscale.com/tstest"
)

// TestPeerCapMap tests that the node capability map (CapMap) is included in peer information.
func TestPeerCapMap(t *testing.T) {
	tstest.Shard(t)
	tstest.Parallel(t)
	env := NewTestEnv(t)

	// Spin up two nodes.
	n1 := NewTestNode(t, env)
	d1 := n1.StartDaemon()
	n1.AwaitListening()
	n1.MustUp()
	n1.AwaitRunning()

	n2 := NewTestNode(t, env)
	d2 := n2.StartDaemon()
	n2.AwaitListening()
	n2.MustUp()
	n2.AwaitRunning()

	n1.AwaitIP4()
	n2.AwaitIP4()

	// Get the nodes from the control server.
	nodes := env.Control.AllNodes()
	if len(nodes) != 2 {
		t.Fatalf("expected 2 nodes, got %d nodes", len(nodes))
	}

	// Figure out which node is which by comparing keys.
	st1 := n1.MustStatus()
	var tn1, tn2 *tailcfg.Node
	for _, n := range nodes {
		if n.Key == st1.Self.PublicKey {
			tn1 = n
		} else {
			tn2 = n
		}
	}

	// Set CapMap on both nodes.
	caps := make(tailcfg.NodeCapMap)
	caps["example:custom"] = []tailcfg.RawMessage{`"value"`}
	caps["example:enabled"] = []tailcfg.RawMessage{`true`}

	env.Control.SetNodeCapMap(tn1.Key, caps)
	env.Control.SetNodeCapMap(tn2.Key, caps)

	// Check that nodes see each other's CapMap.
	if err := tstest.WaitFor(10*time.Second, func() error {
		st1 := n1.MustStatus()
		st2 := n2.MustStatus()

		if len(st1.Peer) == 0 || len(st2.Peer) == 0 {
			return errors.New("no peers")
		}

		// Check n1 sees n2's CapMap.
		p1 := st1.Peer[st1.Peers()[0]]
		if p1.CapMap == nil {
			return errors.New("peer CapMap is nil")
		}
		if p1.CapMap["example:custom"] == nil || p1.CapMap["example:enabled"] == nil {
			return errors.New("peer CapMap missing entries")
		}

		// Check n2 sees n1's CapMap.
		p2 := st2.Peer[st2.Peers()[0]]
		if p2.CapMap == nil {
			return errors.New("peer CapMap is nil")
		}
		if p2.CapMap["example:custom"] == nil || p2.CapMap["example:enabled"] == nil {
			return errors.New("peer CapMap missing entries")
		}

		return nil
	}); err != nil {
		t.Fatal(err)
	}

	d1.MustCleanShutdown(t)
	d2.MustCleanShutdown(t)
}

// TestSetNodeCapMap tests that SetNodeCapMap updates are propagated to peers.
func TestSetNodeCapMap(t *testing.T) {
	tstest.Shard(t)
	tstest.Parallel(t)
	env := NewTestEnv(t)

	n1 := NewTestNode(t, env)
	d1 := n1.StartDaemon()
	n1.AwaitListening()
	n1.MustUp()
	n1.AwaitRunning()

	nodes := env.Control.AllNodes()
	if len(nodes) != 1 {
		t.Fatalf("expected 1 node, got %d nodes", len(nodes))
	}
	node1 := nodes[0]

	// Set initial CapMap.
	caps := make(tailcfg.NodeCapMap)
	caps["test:state"] = []tailcfg.RawMessage{`"initial"`}
	env.Control.SetNodeCapMap(node1.Key, caps)

	// Start second node and verify it sees the first node's CapMap.
	n2 := NewTestNode(t, env)
	d2 := n2.StartDaemon()
	n2.AwaitListening()
	n2.MustUp()
	n2.AwaitRunning()

	if err := tstest.WaitFor(5*time.Second, func() error {
		st := n2.MustStatus()
		if len(st.Peer) == 0 {
			return errors.New("no peers")
		}
		p := st.Peer[st.Peers()[0]]
		if p.CapMap == nil || p.CapMap["test:state"] == nil {
			return errors.New("peer CapMap not set")
		}
		if string(p.CapMap["test:state"][0]) != `"initial"` {
			return errors.New("wrong CapMap value")
		}
		return nil
	}); err != nil {
		t.Fatal(err)
	}

	d1.MustCleanShutdown(t)
	d2.MustCleanShutdown(t)
}
