package logscanner

import (
	"regexp"
	"strings"
	"testing"
	"unicode/utf8"
)

func TestTruncateUTF8(t *testing.T) {
	cases := []struct {
		name     string
		in       string
		maxBytes int
		want     string
	}{
		{
			name:     "shorter than cap untouched",
			in:       "hello",
			maxBytes: 100,
			want:     "hello",
		},
		{
			name:     "ASCII truncation",
			in:       "abcdefghij",
			maxBytes: 5,
			want:     "abcde…",
		},
		{
			name:     "cuts on rune boundary inside multibyte",
			in:       "abcdé",
			maxBytes: 5,
			want:     "abcd…",
		},
		{
			name:     "preserves valid utf-8 when cap lands mid-codepoint",
			in:       "ééééééé",
			maxBytes: 5,
			want:     "éé…",
		},
		{
			name:     "empty input untouched",
			in:       "",
			maxBytes: 5,
			want:     "",
		},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := truncateUTF8(c.in, c.maxBytes)
			if got != c.want {
				t.Errorf("got %q want %q", got, c.want)
			}
			if !utf8.ValidString(got) {
				t.Errorf("result not valid UTF-8: %q", got)
			}
		})
	}
}

func TestNonEmpty(t *testing.T) {
	if nonEmpty("a", "b") != "a" {
		t.Error("first non-empty wins")
	}
	if nonEmpty("", "b") != "b" {
		t.Error("fallback when first empty")
	}
	if nonEmpty("", "") != "" {
		t.Error("both empty yields empty")
	}
}

func TestIndexName_UnamedGroupsStable(t *testing.T) {
	// Each unnamed group should get a distinct fallback name so
	// JSON serialization doesn't collapse $4..$N onto a single key.
	re := mustCompile(t, `(\w+) (\w+) (\w+) (\w+) (\w+)`)
	seen := map[string]bool{}
	for i := 1; i <= 5; i++ {
		name := indexName(re, i)
		if seen[name] {
			t.Errorf("indexName(%d) = %q collides with prior group", i, name)
		}
		seen[name] = true
		if !strings.HasPrefix(name, "$") {
			t.Errorf("unnamed group %d should fall back to $N form, got %q", i, name)
		}
	}
}

func TestIndexName_NamedGroupWins(t *testing.T) {
	re := mustCompile(t, `(?P<code>\d+) (\w+)`)
	if got := indexName(re, 1); got != "code" {
		t.Errorf("named group should win: %q", got)
	}
	if got := indexName(re, 2); got != "$2" {
		t.Errorf("second (unnamed) group: %q", got)
	}
}

// mustCompile is a local helper so the test file is self-contained.
func mustCompile(t *testing.T, pattern string) interface{ SubexpNames() []string } {
	t.Helper()
	r, err := regexp.Compile(pattern)
	if err != nil {
		t.Fatalf("compile %q: %v", pattern, err)
	}
	return r
}
