package commands_test

import (
	"strings"
	"testing"

	"github.com/honeybbq/teamspeak-go/commands"
)

const (
	cmdClientlist = "clientlist"
	strHelloWorld = "hello world"
)

func TestEscapeSpecialChars(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"", ""},
		{"hello", "hello"},
		{strHelloWorld, "hello\\sworld"},
		{"a\\b", "a\\\\b"},
		{"a/b", "a\\/b"},
		{"a|b", "a\\pb"},
		{"a\ab", "a\\ab"},
		{"a\bb", "a\\bb"},
		{"a\fb", "a\\fb"},
		{"a\nb", "a\\nb"},
		{"a\rb", "a\\rb"},
		{"a\tb", "a\\tb"},
		{"a\vb", "a\\vb"},
		{"back\\slash /slash |pipe", "back\\\\slash\\s\\/slash\\s\\ppipe"},
	}
	for _, tt := range tests {
		got := commands.Escape(tt.input)
		if got != tt.expected {
			t.Errorf("Escape(%q) = %q, want %q", tt.input, got, tt.expected)
		}
	}
}

func TestUnescapeSpecialChars(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{"", ""},
		{"hello", "hello"},
		{"hello\\sworld", strHelloWorld},
		{"a\\\\b", "a\\b"},
		{"a\\/b", "a/b"},
		{"a\\pb", "a|b"},
		{"a\\ab", "a\ab"},
		{"a\\bb", "a\bb"},
		{"a\\fb", "a\fb"},
		{"a\\nb", "a\nb"},
		{"a\\rb", "a\rb"},
		{"a\\tb", "a\tb"},
		{"a\\vb", "a\vb"},
	}
	for _, tt := range tests {
		got := commands.Unescape(tt.input)
		if got != tt.expected {
			t.Errorf("Unescape(%q) = %q, want %q", tt.input, got, tt.expected)
		}
	}
}

func TestEscapeUnescapeRoundtrip(t *testing.T) {
	inputs := []string{
		strHelloWorld,
		"back\\slash",
		"pipe|val",
		"slash/val",
		"all\\ \t\n\r",
		"",
		"plain text",
		"mix\\ed /chars|here",
	}
	for _, input := range inputs {
		got := commands.Unescape(commands.Escape(input))
		if got != input {
			t.Errorf("Unescape(Escape(%q)) = %q", input, got)
		}
	}
}

func TestParseCommandEmpty(t *testing.T) {
	if commands.ParseCommand("") != nil {
		t.Error("expected nil for empty string")
	}
}

func TestParseCommandNameOnly(t *testing.T) {
	cmd := commands.ParseCommand(cmdClientlist)
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if cmd.Name != cmdClientlist {
		t.Errorf("Name = %q, want %q", cmd.Name, cmdClientlist)
	}
	if len(cmd.Params) != 0 {
		t.Errorf("expected 0 params, got %d", len(cmd.Params))
	}
}

func TestParseCommandWithParams(t *testing.T) {
	cmd := commands.ParseCommand("notifytextmessage targetmode=3 msg=hello\\sworld invokerid=42")
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if cmd.Name != "notifytextmessage" {
		t.Errorf("Name = %q", cmd.Name)
	}
	if cmd.Params["targetmode"] != "3" {
		t.Errorf("targetmode = %q", cmd.Params["targetmode"])
	}
	if cmd.Params["msg"] != strHelloWorld {
		t.Errorf("msg = %q", cmd.Params["msg"])
	}
	if cmd.Params["invokerid"] != "42" {
		t.Errorf("invokerid = %q", cmd.Params["invokerid"])
	}
}

func TestParseCommandFlagParam(t *testing.T) {
	cmd := commands.ParseCommand("serverinfo -virtualserver_flag")
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if _, ok := cmd.Params["-virtualserver_flag"]; !ok {
		t.Error("expected flag param to exist")
	}
	if cmd.Params["-virtualserver_flag"] != "" {
		t.Errorf("flag param value should be empty, got %q", cmd.Params["-virtualserver_flag"])
	}
}

func TestParseCommandNamelessDataRow(t *testing.T) {
	// TS3 list responses (clientlist/channellist) arrive as nameless rows
	// beginning with a key=value pair. The first field must NOT be dropped as
	// a command name, otherwise the leading field (e.g. clid) is lost.
	cmd := commands.ParseCommand("clid=5761 cid=1 client_nickname=30k")
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if cmd.Name != "" {
		t.Errorf("Name = %q, want empty for nameless data row", cmd.Name)
	}
	if cmd.Params["clid"] != "5761" {
		t.Errorf("clid = %q, want 5761 (leading field must not be dropped)", cmd.Params["clid"])
	}
	if cmd.Params["cid"] != "1" {
		t.Errorf("cid = %q, want 1", cmd.Params["cid"])
	}
	if cmd.Params["client_nickname"] != "30k" {
		t.Errorf("client_nickname = %q, want 30k", cmd.Params["client_nickname"])
	}
}

func TestParseCommandLeadingNonPrintable(t *testing.T) {
	input := "\x00\x01\x02" + cmdClientlist + " clid=1"
	cmd := commands.ParseCommand(input)
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if cmd.Name != cmdClientlist {
		t.Errorf("Name = %q, want %s", cmd.Name, cmdClientlist)
	}
	if cmd.Params["clid"] != "1" {
		t.Errorf("clid = %q", cmd.Params["clid"])
	}
}

func TestParseCommandEscapedValues(t *testing.T) {
	cmd := commands.ParseCommand("test key=hello\\sworld pipe=a\\pb slash=a\\/b")
	if cmd == nil {
		t.Fatal("expected non-nil")
	}
	if cmd.Params["key"] != strHelloWorld {
		t.Errorf("key = %q", cmd.Params["key"])
	}
	if cmd.Params["pipe"] != "a|b" {
		t.Errorf("pipe = %q", cmd.Params["pipe"])
	}
	if cmd.Params["slash"] != "a/b" {
		t.Errorf("slash = %q", cmd.Params["slash"])
	}
}

func TestBuildCommandNoParams(t *testing.T) {
	result := commands.BuildCommand(cmdClientlist, nil)
	if result != cmdClientlist {
		t.Errorf("BuildCommand = %q, want %q", result, cmdClientlist)
	}
}

func TestBuildCommandEscapesValues(t *testing.T) {
	result := commands.BuildCommand("sendtextmessage", map[string]string{
		"msg": strHelloWorld,
	})
	if !strings.Contains(result, "msg=hello\\sworld") {
		t.Errorf("BuildCommand missing escaped value: %q", result)
	}
	if !strings.HasPrefix(result, "sendtextmessage") {
		t.Errorf("BuildCommand missing command name: %q", result)
	}
}

func TestBuildCommandOrderedPreservesOrder(t *testing.T) {
	result := commands.BuildCommandOrdered("clientinitiv", [][2]string{
		{"alpha", "aaa"},
		{"omega", "bbb"},
		{"ot", "1"},
		{"ip", ""},
	})
	expected := "clientinitiv alpha=aaa omega=bbb ot=1 ip="
	if result != expected {
		t.Errorf("BuildCommandOrdered = %q, want %q", result, expected)
	}
}

func TestBuildCommandOrderedEscapesValues(t *testing.T) {
	result := commands.BuildCommandOrdered("cmd", [][2]string{
		{"key", "a b|c"},
	})
	if !strings.Contains(result, "key=a\\sb\\pc") {
		t.Errorf("BuildCommandOrdered missing escaped: %q", result)
	}
}

func TestCommandBuildMethod(t *testing.T) {
	cmd := &commands.Command{
		Name:   "sendtextmessage",
		Params: map[string]string{"msg": "hi"},
	}
	result := cmd.Build()
	if !strings.HasPrefix(result, "sendtextmessage") {
		t.Errorf("Build() = %q, missing command name", result)
	}
	if !strings.Contains(result, "msg=hi") {
		t.Errorf("Build() = %q, missing param", result)
	}
}

func TestParseBuildRoundtrip(t *testing.T) {
	original := commands.BuildCommandOrdered("test", [][2]string{
		{"key1", "val1"},
		{"key2", strHelloWorld},
		{"key3", "pipe|val"},
		{"key4", "back\\slash"},
	})
	cmd := commands.ParseCommand(original)
	if cmd == nil {
		t.Fatal("ParseCommand returned nil")
	}
	if cmd.Name != "test" {
		t.Errorf("Name = %q", cmd.Name)
	}
	if cmd.Params["key1"] != "val1" {
		t.Errorf("key1 = %q", cmd.Params["key1"])
	}
	if cmd.Params["key2"] != strHelloWorld {
		t.Errorf("key2 = %q", cmd.Params["key2"])
	}
	if cmd.Params["key3"] != "pipe|val" {
		t.Errorf("key3 = %q", cmd.Params["key3"])
	}
	if cmd.Params["key4"] != "back\\slash" {
		t.Errorf("key4 = %q", cmd.Params["key4"])
	}
}
