package cmd

import (
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
	"time"

	"github.com/gorilla/websocket"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"heckel.io/ntfy/v2/client"
	"heckel.io/ntfy/v2/test"
	"heckel.io/ntfy/v2/util"
)

func TestCLI_Serve_Unix_Curl(t *testing.T) {
	sockFile := filepath.Join(t.TempDir(), "ntfy.sock")
	configFile := newEmptyFile(t) // Avoid issues with existing server.yml file on system
	go func() {
		app, _, _, _ := newTestApp()
		err := app.Run([]string{"ntfy", "serve", "--config=" + configFile, "--listen-http=-", "--listen-unix=" + sockFile})
		require.Nil(t, err)
	}()
	for i := 0; i < 40 && !util.FileExists(sockFile); i++ {
		time.Sleep(50 * time.Millisecond)
	}
	require.True(t, util.FileExists(sockFile))

	cmd := exec.Command("curl", "-s", "--unix-socket", sockFile, "-d", "this is a message", "localhost/mytopic")
	out, err := cmd.Output()
	require.Nil(t, err)
	m := toMessage(t, string(out))
	require.Equal(t, "this is a message", m.Message)
}

func TestCLI_Serve_WebSocket(t *testing.T) {
	port := 10000 + rand.Intn(20000)
	go func() {
		configFile := newEmptyFile(t) // Avoid issues with existing server.yml file on system
		app, _, _, _ := newTestApp()
		err := app.Run([]string{"ntfy", "serve", "--config=" + configFile, fmt.Sprintf("--listen-http=:%d", port)})
		require.Nil(t, err)
	}()
	test.WaitForPortUp(t, port)

	ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d/mytopic/ws", port), nil)
	require.Nil(t, err)

	messageType, data, err := ws.ReadMessage()
	require.Nil(t, err)
	require.Equal(t, websocket.TextMessage, messageType)
	require.Equal(t, "open", toMessage(t, string(data)).Event)

	c := client.New(client.NewConfig())
	_, err = c.Publish(fmt.Sprintf("http://127.0.0.1:%d/mytopic", port), "my message")
	require.Nil(t, err)

	messageType, data, err = ws.ReadMessage()
	require.Nil(t, err)
	require.Equal(t, websocket.TextMessage, messageType)

	m := toMessage(t, string(data))
	require.Equal(t, "my message", m.Message)
	require.Equal(t, "mytopic", m.Topic)
}

func TestIP_Host_Parsing(t *testing.T) {
	cases := map[string]string{
		"1.1.1.1":          "1.1.1.1/32",
		"fd00::1234":       "fd00::1234/128",
		"192.168.0.3/24":   "192.168.0.0/24",
		"10.1.2.3/8":       "10.0.0.0/8",
		"201:be93::4a6/21": "201:b800::/21",
	}
	for q, expectedAnswer := range cases {
		ips, err := parseIPHostPrefix(q)
		require.Nil(t, err)
		assert.Equal(t, 1, len(ips))
		assert.Equal(t, expectedAnswer, ips[0].String())
	}
}

func newEmptyFile(t *testing.T) string {
	filename := filepath.Join(t.TempDir(), "empty")
	require.Nil(t, os.WriteFile(filename, []byte{}, 0600))
	return filename
}
