package rules

import (
	"testing"

	expect "github.com/yusing/goutils/testing"
)

func TestExtractExpr(t *testing.T) {
	tests := []struct {
		name     string
		in       string
		wantT    MatcherType
		wantExpr string
	}{
		{
			name:     "string implicit",
			in:       "foo",
			wantT:    MatcherTypeString,
			wantExpr: "foo",
		},
		{
			name:     "string explicit",
			in:       "string(`foo`)",
			wantT:    MatcherTypeString,
			wantExpr: "foo",
		},
		{
			name:     "glob",
			in:       "glob(foo)",
			wantT:    MatcherTypeGlob,
			wantExpr: "foo",
		},
		{
			name:     "glob quoted",
			in:       "glob(`foo`)",
			wantT:    MatcherTypeGlob,
			wantExpr: "foo",
		},
		{
			name:     "regex",
			in:       "regex(^[A-Z]+$)",
			wantT:    MatcherTypeRegex,
			wantExpr: "^[A-Z]+$",
		},
		{
			name:     "regex quoted",
			in:       "regex(`^[A-Z]+$`)",
			wantT:    MatcherTypeRegex,
			wantExpr: "^[A-Z]+$",
		},
		{
			name:     "regex with parentheses",
			in:       "regex(test(group))",
			wantT:    MatcherTypeRegex,
			wantExpr: "test(group)",
		},
		{
			name:     "regex complex",
			in:       `regex("^(_next/static|_next/image|favicon.ico).*$")`,
			wantT:    MatcherTypeRegex,
			wantExpr: "^(_next/static|_next/image|favicon.ico).*$",
		},
		{
			name:     "quoted expr",
			in:       "glob(`'foo'`)",
			wantT:    MatcherTypeGlob,
			wantExpr: "'foo'",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			typ, expr, err := ExtractExpr(tt.in)
			expect.NoError(t, err)
			expect.Equal(t, tt.wantT, typ)
			expect.Equal(t, tt.wantExpr, expr)
		})
	}
}

func TestExtractExprInvalid(t *testing.T) {
	tests := []struct {
		name    string
		in      string
		wantErr string
	}{
		{
			name:    "missing closing quote",
			in:      "glob(`foo)",
			wantErr: "unterminated quotes",
		},
		{
			name:    "missing closing bracket",
			in:      "glob(`foo",
			wantErr: "unterminated brackets",
		},
		{
			name:    "invalid matcher type",
			in:      "invalid(`foo`)",
			wantErr: "invalid matcher type: invalid",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, err := ExtractExpr(tt.in)
			expect.HasError(t, err)
			expect.ErrorContains(t, err, tt.wantErr)
		})
	}
}

func TestNegated(t *testing.T) {
	tests := []struct {
		name string
		expr string
		in   string
		want bool
	}{
		{
			name: "negated_string_match",
			expr: "!string(`foo`)",
			in:   "foo",
			want: false,
		},
		{
			name: "negated_string_no_match",
			expr: "!string(`foo`)",
			in:   "bar",
			want: true,
		},
		{
			name: "negated_glob_match",
			expr: "!glob(`foo`)",
			in:   "foo",
			want: false,
		},
		{
			name: "negated_glob_no_match",
			expr: "!glob(`foo`)",
			in:   "bar",
			want: true,
		},
		{
			name: "negated_regex_match",
			expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
			in:   "favicon.ico",
			want: false,
		},
		{
			name: "negated_regex_no_match",
			expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
			in:   "bar",
			want: true,
		},
		{
			name: "negated_regex_no_match2",
			expr: "!regex(`^(_next/static|_next/image|favicon.ico).*$`)",
			in:   "/",
			want: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			matcher, err := ParseMatcher(tt.expr)
			expect.NoError(t, err)
			expect.Equal(t, tt.want, matcher(tt.in))
		})
	}
}
