package tailnet_test

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
	"tailscale.com/tailcfg"

	"github.com/coder/coder/v2/tailnet"
)

func TestNewDERPMap(t *testing.T) {
	t.Parallel()
	t.Run("WithoutRemoteURL", func(t *testing.T) {
		t.Parallel()
		derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
			RegionID: 1,
			Nodes:    []*tailcfg.DERPNode{{}},
		}, []string{"stun.google.com:2345"}, "", "", false)
		require.NoError(t, err)
		require.Len(t, derpMap.Regions, 2)
		require.Len(t, derpMap.Regions[1].Nodes, 1)
		require.Len(t, derpMap.Regions[2].Nodes, 1)
	})
	t.Run("RemoteURL", func(t *testing.T) {
		t.Parallel()
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			data, _ := json.Marshal(&tailcfg.DERPMap{
				Regions: map[int]*tailcfg.DERPRegion{
					1: {},
				},
			})
			_, _ = w.Write(data)
		}))
		t.Cleanup(server.Close)
		derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
			RegionID: 2,
		}, []string{}, server.URL, "", false)
		require.NoError(t, err)
		require.Len(t, derpMap.Regions, 2)
	})
	t.Run("RemoteConflicts", func(t *testing.T) {
		t.Parallel()
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			data, _ := json.Marshal(&tailcfg.DERPMap{
				Regions: map[int]*tailcfg.DERPRegion{
					1: {},
				},
			})
			_, _ = w.Write(data)
		}))
		t.Cleanup(server.Close)
		_, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
			RegionID: 1,
		}, []string{}, server.URL, "", false)
		require.Error(t, err)
	})
	t.Run("LocalPath", func(t *testing.T) {
		t.Parallel()
		localPath := filepath.Join(t.TempDir(), "derp.json")
		content, err := json.Marshal(&tailcfg.DERPMap{
			Regions: map[int]*tailcfg.DERPRegion{
				1: {},
			},
		})
		require.NoError(t, err)
		err = os.WriteFile(localPath, content, 0o600)
		require.NoError(t, err)
		derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
			RegionID: 2,
		}, []string{}, "", localPath, false)
		require.NoError(t, err)
		require.Len(t, derpMap.Regions, 2)
	})
	t.Run("DisableSTUN", func(t *testing.T) {
		t.Parallel()
		localPath := filepath.Join(t.TempDir(), "derp.json")
		content, err := json.Marshal(&tailcfg.DERPMap{
			Regions: map[int]*tailcfg.DERPRegion{
				1: {
					Nodes: []*tailcfg.DERPNode{{
						STUNPort: 1234,
					}},
				},
				2: {
					Nodes: []*tailcfg.DERPNode{
						{
							STUNPort: 1234,
						},
						{
							STUNPort: 12345,
						},
						{
							STUNOnly: true,
							STUNPort: 54321,
						},
					},
				},
			},
		})
		require.NoError(t, err)
		err = os.WriteFile(localPath, content, 0o600)
		require.NoError(t, err)
		region := &tailcfg.DERPRegion{
			RegionID: 3,
			Nodes: []*tailcfg.DERPNode{{
				STUNPort: 1234,
			}},
		}
		derpMap, err := tailnet.NewDERPMap(context.Background(), region, []string{"127.0.0.1:54321"}, "", localPath, true)
		require.NoError(t, err)
		require.Len(t, derpMap.Regions, 3)

		require.Len(t, derpMap.Regions[1].Nodes, 1)
		require.EqualValues(t, -1, derpMap.Regions[1].Nodes[0].STUNPort)
		// The STUNOnly node should get removed.
		require.Len(t, derpMap.Regions[2].Nodes, 2)
		require.EqualValues(t, -1, derpMap.Regions[2].Nodes[0].STUNPort)
		require.False(t, derpMap.Regions[2].Nodes[0].STUNOnly)
		require.EqualValues(t, -1, derpMap.Regions[2].Nodes[1].STUNPort)
		require.False(t, derpMap.Regions[2].Nodes[1].STUNOnly)
		// We don't add any nodes ourselves if STUN is disabled.
		require.Len(t, derpMap.Regions[3].Nodes, 1)
		// ... but we still remove the STUN port from existing nodes in the
		// region.
		require.EqualValues(t, -1, derpMap.Regions[3].Nodes[0].STUNPort)
	})
}
