package stringsupport

import (
	"bytes"
	"testing"
)

func TestUTF8ToSJIS(t *testing.T) {
	tests := []struct {
		name  string
		input string
	}{
		{"ascii", "Hello World"},
		{"numbers", "12345"},
		{"symbols", "!@#$%"},
		{"japanese_hiragana", "あいうえお"},
		{"japanese_katakana", "アイウエオ"},
		{"japanese_kanji", "日本語"},
		{"mixed", "Hello世界"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := UTF8ToSJIS(tt.input)
			if len(result) == 0 && len(tt.input) > 0 {
				t.Error("UTF8ToSJIS returned empty result for non-empty input")
			}
		})
	}
}

func TestSJISToUTF8(t *testing.T) {
	// Test ASCII characters (which are the same in SJIS and UTF-8)
	asciiBytes := []byte("Hello World")
	result, err := SJISToUTF8(asciiBytes)
	if err != nil {
		t.Fatalf("SJISToUTF8() unexpected error: %v", err)
	}
	if result != "Hello World" {
		t.Errorf("SJISToUTF8() = %q, want %q", result, "Hello World")
	}
}

func TestUTF8ToSJIS_RoundTrip(t *testing.T) {
	// Test round-trip conversion for ASCII
	original := "Hello World 123"
	sjis := UTF8ToSJIS(original)
	back, _ := SJISToUTF8(sjis)

	if back != original {
		t.Errorf("Round-trip failed: got %q, want %q", back, original)
	}
}

func TestToNGWord(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		minLen  int
		checkFn func(t *testing.T, result []uint16)
	}{
		{
			name:   "ascii characters",
			input:  "ABC",
			minLen: 3,
			checkFn: func(t *testing.T, result []uint16) {
				if result[0] != uint16('A') {
					t.Errorf("result[0] = %d, want %d", result[0], 'A')
				}
			},
		},
		{
			name:   "numbers",
			input:  "123",
			minLen: 3,
			checkFn: func(t *testing.T, result []uint16) {
				if result[0] != uint16('1') {
					t.Errorf("result[0] = %d, want %d", result[0], '1')
				}
			},
		},
		{
			name:   "japanese characters",
			input:  "あ",
			minLen: 1,
			checkFn: func(t *testing.T, result []uint16) {
				if len(result) == 0 {
					t.Error("result should not be empty")
				}
			},
		},
		{
			name:   "empty string",
			input:  "",
			minLen: 0,
			checkFn: func(t *testing.T, result []uint16) {
				if len(result) != 0 {
					t.Errorf("result length = %d, want 0", len(result))
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ToNGWord(tt.input)
			if len(result) < tt.minLen {
				t.Errorf("ToNGWord() length = %d, want at least %d", len(result), tt.minLen)
			}
			if tt.checkFn != nil {
				tt.checkFn(t, result)
			}
		})
	}
}

func TestPaddedString(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		size      uint
		transform bool
		wantLen   uint
	}{
		{"short string", "Hello", 10, false, 10},
		{"exact size", "Test", 5, false, 5},
		{"longer than size", "This is a long string", 10, false, 10},
		{"empty string", "", 5, false, 5},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := PaddedString(tt.input, tt.size, tt.transform)
			if uint(len(result)) != tt.wantLen {
				t.Errorf("PaddedString() length = %d, want %d", len(result), tt.wantLen)
			}
			// Verify last byte is null
			if result[len(result)-1] != 0 {
				t.Error("PaddedString() should end with null byte")
			}
		})
	}
}

func TestPaddedString_NullTermination(t *testing.T) {
	result := PaddedString("Test", 10, false)
	if result[9] != 0 {
		t.Error("Last byte should be null")
	}
	// First 4 bytes should be "Test"
	if !bytes.Equal(result[0:4], []byte("Test")) {
		t.Errorf("First 4 bytes = %v, want %v", result[0:4], []byte("Test"))
	}
}

func TestCSVAdd(t *testing.T) {
	tests := []struct {
		name     string
		csv      string
		value    int
		expected string
	}{
		{"add to empty", "", 1, "1"},
		{"add to existing", "1,2,3", 4, "1,2,3,4"},
		{"add duplicate", "1,2,3", 2, "1,2,3"},
		{"add to single", "5", 10, "5,10"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVAdd(tt.csv, tt.value)
			if result != tt.expected {
				t.Errorf("CSVAdd(%q, %d) = %q, want %q", tt.csv, tt.value, result, tt.expected)
			}
		})
	}
}

func TestCSVRemove(t *testing.T) {
	tests := []struct {
		name  string
		csv   string
		value int
		check func(t *testing.T, result string)
	}{
		{
			name:  "remove from middle",
			csv:   "1,2,3,4,5",
			value: 3,
			check: func(t *testing.T, result string) {
				if CSVContains(result, 3) {
					t.Error("Result should not contain 3")
				}
				if CSVLength(result) != 4 {
					t.Errorf("Result length = %d, want 4", CSVLength(result))
				}
			},
		},
		{
			name:  "remove from start",
			csv:   "1,2,3",
			value: 1,
			check: func(t *testing.T, result string) {
				if CSVContains(result, 1) {
					t.Error("Result should not contain 1")
				}
			},
		},
		{
			name:  "remove non-existent",
			csv:   "1,2,3",
			value: 99,
			check: func(t *testing.T, result string) {
				if CSVLength(result) != 3 {
					t.Errorf("Length should remain 3, got %d", CSVLength(result))
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVRemove(tt.csv, tt.value)
			tt.check(t, result)
		})
	}
}

func TestCSVContains(t *testing.T) {
	tests := []struct {
		name     string
		csv      string
		value    int
		expected bool
	}{
		{"contains in middle", "1,2,3,4,5", 3, true},
		{"contains at start", "1,2,3", 1, true},
		{"contains at end", "1,2,3", 3, true},
		{"does not contain", "1,2,3", 5, false},
		{"empty csv", "", 1, false},
		{"single value match", "42", 42, true},
		{"single value no match", "42", 43, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVContains(tt.csv, tt.value)
			if result != tt.expected {
				t.Errorf("CSVContains(%q, %d) = %v, want %v", tt.csv, tt.value, result, tt.expected)
			}
		})
	}
}

func TestCSVLength(t *testing.T) {
	tests := []struct {
		name     string
		csv      string
		expected int
	}{
		{"empty", "", 0},
		{"single", "1", 1},
		{"multiple", "1,2,3,4,5", 5},
		{"two", "10,20", 2},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVLength(tt.csv)
			if result != tt.expected {
				t.Errorf("CSVLength(%q) = %d, want %d", tt.csv, result, tt.expected)
			}
		})
	}
}

func TestCSVElems(t *testing.T) {
	tests := []struct {
		name     string
		csv      string
		expected []int
	}{
		{"empty", "", []int{}},
		{"single", "42", []int{42}},
		{"multiple", "1,2,3,4,5", []int{1, 2, 3, 4, 5}},
		{"negative numbers", "-1,0,1", []int{-1, 0, 1}},
		{"large numbers", "100,200,300", []int{100, 200, 300}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVElems(tt.csv)
			if len(result) != len(tt.expected) {
				t.Errorf("CSVElems(%q) length = %d, want %d", tt.csv, len(result), len(tt.expected))
			}
			for i, v := range tt.expected {
				if i >= len(result) || result[i] != v {
					t.Errorf("CSVElems(%q)[%d] = %d, want %d", tt.csv, i, result[i], v)
				}
			}
		})
	}
}

func TestCSVGetIndex(t *testing.T) {
	csv := "10,20,30,40,50"

	tests := []struct {
		name     string
		index    int
		expected int
	}{
		{"first", 0, 10},
		{"middle", 2, 30},
		{"last", 4, 50},
		{"out of bounds", 10, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVGetIndex(csv, tt.index)
			if result != tt.expected {
				t.Errorf("CSVGetIndex(%q, %d) = %d, want %d", csv, tt.index, result, tt.expected)
			}
		})
	}
}

func TestCSVSetIndex(t *testing.T) {
	tests := []struct {
		name  string
		csv   string
		index int
		value int
		check func(t *testing.T, result string)
	}{
		{
			name:  "set first",
			csv:   "10,20,30",
			index: 0,
			value: 99,
			check: func(t *testing.T, result string) {
				if CSVGetIndex(result, 0) != 99 {
					t.Errorf("Index 0 = %d, want 99", CSVGetIndex(result, 0))
				}
			},
		},
		{
			name:  "set middle",
			csv:   "10,20,30",
			index: 1,
			value: 88,
			check: func(t *testing.T, result string) {
				if CSVGetIndex(result, 1) != 88 {
					t.Errorf("Index 1 = %d, want 88", CSVGetIndex(result, 1))
				}
			},
		},
		{
			name:  "set last",
			csv:   "10,20,30",
			index: 2,
			value: 77,
			check: func(t *testing.T, result string) {
				if CSVGetIndex(result, 2) != 77 {
					t.Errorf("Index 2 = %d, want 77", CSVGetIndex(result, 2))
				}
			},
		},
		{
			name:  "set out of bounds",
			csv:   "10,20,30",
			index: 10,
			value: 99,
			check: func(t *testing.T, result string) {
				// Should not modify the CSV
				if CSVLength(result) != 3 {
					t.Errorf("CSV length changed when setting out of bounds")
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := CSVSetIndex(tt.csv, tt.index, tt.value)
			tt.check(t, result)
		})
	}
}

func TestCSV_CompleteWorkflow(t *testing.T) {
	// Test a complete workflow
	csv := ""

	// Add elements
	csv = CSVAdd(csv, 10)
	csv = CSVAdd(csv, 20)
	csv = CSVAdd(csv, 30)

	if CSVLength(csv) != 3 {
		t.Errorf("Length = %d, want 3", CSVLength(csv))
	}

	// Check contains
	if !CSVContains(csv, 20) {
		t.Error("Should contain 20")
	}

	// Get element
	if CSVGetIndex(csv, 1) != 20 {
		t.Errorf("Index 1 = %d, want 20", CSVGetIndex(csv, 1))
	}

	// Set element
	csv = CSVSetIndex(csv, 1, 99)
	if CSVGetIndex(csv, 1) != 99 {
		t.Errorf("Index 1 = %d, want 99 after set", CSVGetIndex(csv, 1))
	}

	// Remove element
	csv = CSVRemove(csv, 99)
	if CSVContains(csv, 99) {
		t.Error("Should not contain 99 after removal")
	}

	if CSVLength(csv) != 2 {
		t.Errorf("Length = %d, want 2 after removal", CSVLength(csv))
	}
}

func BenchmarkCSVAdd(b *testing.B) {
	csv := "1,2,3,4,5"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = CSVAdd(csv, 6)
	}
}

func BenchmarkCSVContains(b *testing.B) {
	csv := "1,2,3,4,5,6,7,8,9,10"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = CSVContains(csv, 5)
	}
}

func BenchmarkCSVRemove(b *testing.B) {
	csv := "1,2,3,4,5,6,7,8,9,10"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = CSVRemove(csv, 5)
	}
}

func BenchmarkCSVElems(b *testing.B) {
	csv := "1,2,3,4,5,6,7,8,9,10"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = CSVElems(csv)
	}
}

func TestSJISToUTF8Lossy(t *testing.T) {
	// Valid SJIS (ASCII subset) decodes correctly.
	got := SJISToUTF8Lossy([]byte("Hello"))
	if got != "Hello" {
		t.Errorf("SJISToUTF8Lossy(valid) = %q, want %q", got, "Hello")
	}

	// Truncated multi-byte SJIS sequence (lead byte 0x82 without trail byte)
	// does not panic and returns some result (lossy).
	got = SJISToUTF8Lossy([]byte{0x82})
	_ = got // must not panic

	// Nil input returns empty string.
	got = SJISToUTF8Lossy(nil)
	if got != "" {
		t.Errorf("SJISToUTF8Lossy(nil) = %q, want %q", got, "")
	}
}

func TestUTF8ToSJIS_UnsupportedCharacters(t *testing.T) {
	// Regression test for PR #116: Characters outside the Shift-JIS range
	// (e.g. Lenny face, cuneiform) previously caused a panic in UTF8ToSJIS,
	// crashing the server when relayed from Discord.
	tests := []struct {
		name  string
		input string
	}{
		{"lenny_face", "( ͡° ͜ʖ ͡°)"},
		{"cuneiform", "𒀜"},
		{"emoji", "Hello 🎮 World"},
		{"mixed_unsupported", "Test ͡° message 𒀜 here"},
		{"zalgo_text", "H̷e̸l̵l̶o̷"},
		{"only_unsupported", "🎮🎲🎯"},
		{"cyrillic", "Привет"},
		{"arabic", "مرحبا"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Must not panic - the old code would panic here
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("UTF8ToSJIS panicked on input %q: %v", tt.input, r)
				}
			}()
			result := UTF8ToSJIS(tt.input)
			if result == nil {
				t.Error("UTF8ToSJIS returned nil")
			}
		})
	}
}

func TestUTF8ToSJIS_PreservesValidContent(t *testing.T) {
	// Verify that valid Shift-JIS content is preserved when mixed with
	// unsupported characters.
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"ascii_with_emoji", "Hello 🎮 World", "Hello  World"},
		{"japanese_with_emoji", "テスト🎮データ", "テストデータ"},
		{"only_valid", "Hello World", "Hello World"},
		{"only_invalid", "🎮🎲🎯", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sjis := UTF8ToSJIS(tt.input)
			roundTripped, _ := SJISToUTF8(sjis)
			if roundTripped != tt.expected {
				t.Errorf("UTF8ToSJIS(%q) round-tripped to %q, want %q", tt.input, roundTripped, tt.expected)
			}
		})
	}
}

func TestToNGWord_UnsupportedCharacters(t *testing.T) {
	// ToNGWord also calls UTF8ToSJIS internally, so it must not panic either.
	inputs := []string{"( ͡° ͜ʖ ͡°)", "🎮", "Hello 🎮 World"}
	for _, input := range inputs {
		t.Run(input, func(t *testing.T) {
			defer func() {
				if r := recover(); r != nil {
					t.Errorf("ToNGWord panicked on input %q: %v", input, r)
				}
			}()
			_ = ToNGWord(input)
		})
	}
}

func BenchmarkUTF8ToSJIS(b *testing.B) {
	text := "Hello World テスト"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = UTF8ToSJIS(text)
	}
}

func BenchmarkSJISToUTF8(b *testing.B) {
	text := []byte("Hello World")
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = SJISToUTF8(text)
	}
}

func BenchmarkPaddedString(b *testing.B) {
	text := "Test String"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = PaddedString(text, 50, false)
	}
}

func BenchmarkToNGWord(b *testing.B) {
	text := "TestString"
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = ToNGWord(text)
	}
}
