// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later

package wildcard

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
)

// TestMatch - Tests validate the logic of wild card matching.
// `Match` supports '*' (zero or more characters) and '?' (one character) wildcards in typical glob style filtering.
// A '*' in a provided string will not result in matching the strings before and after the '*' of the string provided.
// Sample usage: In resource matching for bucket policy validation.
func TestMatch(t *testing.T) {
	t.Parallel()
	tests := []struct {
		pattern string
		text    string
		matched bool
	}{
		{
			pattern: "The?Simpsons*",
			text:    "The Simpsons S12",
			matched: true,
		},
		{
			pattern: "The?Simpsons*",
			text:    "The.Simpsons.S12",
			matched: true,
		},
		{
			pattern: "The?Simpsons*",
			text:    "The.Simps.S12",
			matched: false,
		},
		{
			pattern: "The?Simp",
			text:    "The.Simps.S12",
			matched: false,
		},
		{
			pattern: "The?Simp",
			text:    "The.Simps.S12",
			matched: false,
		},
		{
			pattern: "The*Simp",
			text:    "The.Simp",
			matched: true,
		},
		{
			pattern: "*tv*",
			text:    "tv",
			matched: true,
		},
		{
			pattern: "t?",
			text:    "tv",
			matched: true,
		},
		{
			pattern: "?",
			text:    "z",
			matched: true,
		},
		{
			pattern: "*EPUB*",
			text:    "Translated (Group) / EPUB",
			matched: true,
		},
		{
			pattern: "*EP?B*",
			text:    "ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n Translated (Group) / EPUB WITH OTHER STUFF ON THE OTHER END ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ARG THIS IS A STUPID LONG ONG LONG LONG STRING BEFORE AND AFTER \\n ",
			matched: true,
		},
		{
			pattern: "*shift*",
			text:    "Good show shift S02 2160p ATVP WEB-DL DDP 5.1 Atmos DV HEVC-GROUP",
			matched: true,
		},
		{
			pattern: "The God of the Brr*The Power of Brr",
			text:    "The Power of Brr",
			matched: false,
		},
		{
			pattern: "The God of the Brr*The Power of Brr",
			text:    "The God of the Brr",
			matched: false,
		},
		{
			pattern: "The God of the Brr*The Power of Brr",
			text:    "The God of the Brr The Power of Brr",
			matched: true,
		},
		{
			pattern: "The God of the Brr*The Power of Brr",
			text:    "The God of the Brr - The Power of Brr",
			matched: true,
		},
		{
			pattern: "The God of the Brr*The Power of Brr",
			text:    "The God of the BrrThe Power of Brr",
			matched: true,
		},
		{
			pattern: "mysteries?of?the?abandoned*",
			text:    "them",
			matched: false,
		},
		{
			pattern: "t?q*",
			text:    "tam e",
			matched: false,
		},
		{
			pattern: "Hard?Quiz*",
			text:    "HardX 24 10 12 Ella Reese XXX 1080p MP4-WRB",
			matched: false,
		},
		{
			pattern: "Hard?Quiz*",
			text:    "HardX",
			matched: false,
		},
		{
			pattern: "T?Q*",
			text:    "T?Q",
			matched: true,
		},
		{
			pattern: "Lee*",
			text:    "Let Go",
			matched: false,
		},
		{
			pattern: "*black?metal*",
			text:    "   ||  Artist......: Vredehammer                                        ||\n   ||  Album.......: Mintaka                                            ||\n   ||  Year........: 2013                                               ||\n   ||                                                                   ||\n   ||  Genre.......: black metal                                        ||\n   ||  Label.......: Indie Recordings                                   ||\n   ||                                                                   ||\n   ||  Source......: FLAC/WEB (16bit)                                   ||\n   ||  Encoder.....: libFLAC                                            ||\n   ||  Bitrate.....: 948 kbps avg.                                      ||\n   ||  F.Rate......: 44.1kHz                                            ||\n   ||                                                                   ||\n   ||  Playtime....: 00:19:27 / 138.70MB                                ||\n   ||  R.Date......: 2024-10-22                                         ||\n   ||  S.Date......: 2013-03-27                                         ||\n   ||                                                                   ||\n   ||                                                                   ||\n   ||  01. The King Has Risen                                  3:53     ||\n   ||  02. H├╕ster av sjeler                                    4:17     ||\n   ||  03. Mintaka                                             4:10     ||\n   ||  04. Ditt siste aandedrag                                7:07     ||\n   ||                                                                   ||\n   ||                                                                   ||\n   ||  Vredehammer combines aggressive guitars and Norse melodies.      ||\n   ",
			matched: true,
		},
	}
	for idx, tt := range tests {
		t.Run(fmt.Sprintf("match: %d", idx), func(t *testing.T) {
			actualResult := Match(tt.pattern, tt.text)
			assert.Equal(t, tt.matched, actualResult)

		})
	}
}

func TestMatchSimple(t *testing.T) {
	t.Parallel()
	tests := []struct {
		pattern string
		name    string
		want    bool
	}{
		{"", "", true},
		{"*", "test", true},
		{"t*t", "test", true},
		{"t*t", "tost", true},
		{"t?st", "test", false},
		{"t?st", "tast", false},
		{"test", "test", true},
		{"*te?t*", "test", false},
		{"*test*", "test", true},
		{"test", "toast", false},
		{"", "non-empty", false},
		{"*", "", true},
		{"te*t", "test", true},
		{"te*", "te", true},
		{"te*", "ten", true},
		{"?est", "test", false},
		{"best", "best", true},
	}

	for _, tt := range tests {
		if got := MatchSimple(tt.pattern, tt.name); got != tt.want {
			t.Errorf("MatchSimple(%q, %q) = %v, want %v", tt.pattern, tt.name, got, tt.want)
		}
	}
}

func TestMatchSliceSimple(t *testing.T) {
	t.Parallel()
	tests := []struct {
		patterns []string
		name     string
		want     bool
	}{
		{[]string{"*", "test"}, "test", true},
		{[]string{"te?t", "tost", "random"}, "tost", true},
		{[]string{"te?t", "t?s?", "random"}, "tost", false},
		{[]string{"*st", "n?st", "l*st"}, "list", true},
		{[]string{"?", "?*", "?**"}, "t", false},
		{[]string{"a", "b", "c"}, "d", false},
		{[]string{}, "test", false},
		{[]string{"*"}, "any", true},
		{[]string{"abc", "def", "ghi"}, "ghi", true},
		{[]string{"abc", "def", "ghi"}, "xyz", false},
		{[]string{"abc*", "def*", "ghi*"}, "ghi-test", true},
	}

	for _, tt := range tests {
		if got := MatchSliceSimple(tt.patterns, tt.name); got != tt.want {
			t.Errorf("MatchSliceSimple(%v, %q) = %v, want %v", tt.patterns, tt.name, got, tt.want)
		}
	}
}

func TestMatchSlice(t *testing.T) {
	t.Parallel()
	tests := []struct {
		patterns []string
		name     string
		want     bool
	}{
		{[]string{"*", "test", "t?st"}, "test", true},
		{[]string{"te?t", "t?st", "random"}, "tost", true},
		{[]string{"te?t", "t?s?", "random"}, "tost", true},
		{[]string{"te?t", "t??e?", "random"}, "toser", true},
		{[]string{"*st", "n?st", "l*st"}, "list", true},
		{[]string{"?", "??", "???"}, "t", true},
		{[]string{"a", "b", "c"}, "d", false},
		{[]string{}, "test", false},
		{[]string{"*"}, "any", true},
		{[]string{"abc", "def", "ghi"}, "ghi", true},
		{[]string{"abc", "def", "ghi"}, "xyz", false},
		{[]string{"abc*", "def*", "ghi*"}, "ghi-test", true},
		{[]string{"abc?", "def?", "ghi?"}, "ghiz", true},
		{[]string{"abc?", "def?", "ghi?"}, "ghizz", false},
		{[]string{"a*?", "b*?", "c*?"}, "cwhatever", true},
		{[]string{"a*?", "b*?", "c*?"}, "dwhatever", false},
		{[]string{"*"}, "", true},
		{[]string{"abc"}, "abc", true},
		{[]string{"?bc"}, "abc", true},
		{[]string{"abc*"}, "abcd", true},
		{[]string{"guacamole", "The?Simpsons*"}, "The Simpsons S12", true},
		{[]string{"guacamole*", "The?Sompsons*"}, "The Simpsons S12", false},
		{[]string{"guac?mole*", "The?S?mpson"}, "The Simpsons S12", false},
		{[]string{"guac?mole*", "The?S?mpson"}, "guacamole Tornado", true},
		{[]string{"mole*", "The?S?mpson"}, "guacamole Tornado", false},
		{[]string{"??**mole*", "The?S?mpson"}, "guacamole Tornado", true},
	}

	for _, tt := range tests {
		if got := MatchSlice(tt.patterns, tt.name); got != tt.want {
			t.Errorf("MatchSlice(%v, %q) = %v, want %v", tt.patterns, tt.name, got, tt.want)
		}
	}
}

func Benchmark_Regex(b *testing.B) {
	for i := 0; i < b.N; i++ {
		b.StartTimer()
		TestMatchSlice(nil)
		b.StopTimer()
	}
}
