//  Copyright (c) 2016 Couchbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 		http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package facet

import (
	"os"
	"regexp"
	"testing"
)

var terms []string

func init() {
	wsRegexp := regexp.MustCompile(`\W+`)
	input, err := os.ReadFile("benchmark_data.txt")
	if err != nil {
		panic(err)
	}
	terms = wsRegexp.Split(string(input), -1)
}

func BenchmarkTermsFacet10(b *testing.B) {
	termsFacetN(b, 10)
}

func BenchmarkTermsFacet100(b *testing.B) {
	termsFacetN(b, 100)
}

func BenchmarkTermsFacet1000(b *testing.B) {
	termsFacetN(b, 1000)
}

func BenchmarkTermsFacet10000(b *testing.B) {
	termsFacetN(b, 10000)
}

// func BenchmarkTermsFacet100000(b *testing.B) {
// 	termsFacetN(b, 100000)
// }

func termsFacetN(b *testing.B, numTerms int) {
	field := "test"
	termsLen := len(terms)
	tfb := NewTermsFacetBuilder(field, 3)
	i := 0
	for len(tfb.termsCount) < numTerms && i <= termsLen {
		j := i % termsLen
		term := terms[j]
		tfb.StartDoc()
		tfb.UpdateVisitor([]byte(term))
		tfb.EndDoc()
		i++
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		tfb.Result()
	}
}

func TestTermsFacetPrefix(t *testing.T) {
	field := "category"
	tfb := NewTermsFacetBuilder(field, 10)
	tfb.SetPrefixFilter("prod-")

	// Add terms with various prefixes
	terms := []string{
		"prod-server",
		"prod-database",
		"dev-server",
		"dev-database",
		"test-server",
		"prod-cache",
	}

	for _, term := range terms {
		tfb.StartDoc()
		tfb.UpdateVisitor([]byte(term))
		tfb.EndDoc()
	}

	result := tfb.Result()

	// Should only have terms with "prod-" prefix
	if result.Terms.Len() != 3 {
		t.Fatalf("expected 3 matching terms, got %d", result.Terms.Len())
	}

	// Verify the terms are correct
	expectedTerms := map[string]bool{
		"prod-server":   true,
		"prod-database": true,
		"prod-cache":    true,
	}

	for _, facet := range result.Terms.Terms() {
		if !expectedTerms[facet.Term] {
			t.Errorf("unexpected term in results: %s", facet.Term)
		}
		if facet.Count != 1 {
			t.Errorf("expected count 1 for %s, got %d", facet.Term, facet.Count)
		}
	}

	// Total should include all terms (matching + non-matching)
	if result.Total != 6 {
		t.Errorf("expected total 6, got %d", result.Total)
	}

	// Other should be 3 (the non-matching terms)
	if result.Other != 3 {
		t.Errorf("expected other 3, got %d", result.Other)
	}
}

func TestTermsFacetRegex(t *testing.T) {
	field := "product_code"
	// Match pattern: ABC-#### (3 letters, dash, 4 digits) - pattern: ^[A-Z]{3}-\\d{4}$
	tfb := NewTermsFacetBuilder(field, 10)
	regex, err := regexp.Compile("^[A-Z]{3}-\\d{4}$")
	if err != nil {
		t.Fatal(err)
	}
	tfb.SetRegexFilter(regex)

	// Add terms with various formats
	terms := []string{
		"ABC-1234",
		"XYZ-5678",
		"ABC-999",   // too few digits
		"ABCD-1234", // too many letters
		"ABC-ABCD",  // letters instead of digits
		"DEF-0000",
	}

	for _, term := range terms {
		tfb.StartDoc()
		tfb.UpdateVisitor([]byte(term))
		tfb.EndDoc()
	}

	result := tfb.Result()

	// Should only have 3 terms matching the pattern
	if result.Terms.Len() != 3 {
		t.Fatalf("expected 3 matching terms, got %d", result.Terms.Len())
	}

	// Verify the terms are correct
	expectedTerms := map[string]bool{
		"ABC-1234": true,
		"XYZ-5678": true,
		"DEF-0000": true,
	}

	for _, facet := range result.Terms.Terms() {
		if !expectedTerms[facet.Term] {
			t.Errorf("unexpected term in results: %s", facet.Term)
		}
		if facet.Count != 1 {
			t.Errorf("expected count 1 for %s, got %d", facet.Term, facet.Count)
		}
	}

	// Total should include all terms
	if result.Total != 6 {
		t.Errorf("expected total 6, got %d", result.Total)
	}

	// Other should be 3 (the non-matching terms)
	if result.Other != 3 {
		t.Errorf("expected other 3, got %d", result.Other)
	}
}

func TestTermsFacetPrefixAndRegex(t *testing.T) {
	field := "tag"
	// Both prefix "env:" and regex pattern for prod/staging only
	tfb := NewTermsFacetBuilder(field, 10)
	tfb.SetPrefixFilter("env:")
	regex, err := regexp.Compile("^env:(prod|staging)$")
	if err != nil {
		t.Fatal(err)
	}
	tfb.SetRegexFilter(regex)

	// Add various terms
	terms := []string{
		"env:prod",
		"env:staging",
		"env:dev",     // has prefix but doesn't match regex
		"env:test",    // has prefix but doesn't match regex
		"type:server", // no prefix
		"env:prod",    // duplicate
		"env:staging", // duplicate
	}

	for _, term := range terms {
		tfb.StartDoc()
		tfb.UpdateVisitor([]byte(term))
		tfb.EndDoc()
	}

	result := tfb.Result()

	// Should only have 2 unique terms (env:prod and env:staging)
	if result.Terms.Len() != 2 {
		t.Fatalf("expected 2 matching terms, got %d", result.Terms.Len())
	}

	// Verify the terms and counts
	termCounts := make(map[string]int)
	for _, facet := range result.Terms.Terms() {
		termCounts[facet.Term] = facet.Count
	}

	if termCounts["env:prod"] != 2 {
		t.Errorf("expected count 2 for env:prod, got %d", termCounts["env:prod"])
	}
	if termCounts["env:staging"] != 2 {
		t.Errorf("expected count 2 for env:staging, got %d", termCounts["env:staging"])
	}

	// Total should be all 7 terms
	if result.Total != 7 {
		t.Errorf("expected total 7, got %d", result.Total)
	}

	// Other should be 3 (env:dev, env:test, type:server)
	if result.Other != 3 {
		t.Errorf("expected other 3, got %d", result.Other)
	}
}

func TestTermsFacetInvalidRegex(t *testing.T) {
	// Invalid regex pattern (unmatched bracket)
	_, err := regexp.Compile("[invalid")
	if err == nil {
		t.Fatal("expected error for invalid regex, got nil")
	}
}

func TestTermsFacetNoFilter(t *testing.T) {
	field := "tag"
	tfb := NewTermsFacetBuilder(field, 2)

	terms := []string{"apple", "banana", "cherry", "apple"}

	for _, term := range terms {
		tfb.StartDoc()
		tfb.UpdateVisitor([]byte(term))
		tfb.EndDoc()
	}

	result := tfb.Result()

	// Should return top 2 by count
	if result.Terms.Len() != 2 {
		t.Fatalf("expected 2 terms, got %d", result.Terms.Len())
	}

	// Apple should be first with count 2
	facets := result.Terms.Terms()
	if facets[0].Term != "apple" || facets[0].Count != 2 {
		t.Errorf("expected apple with count 2, got %s with count %d", facets[0].Term, facets[0].Count)
	}

	// Total should be 4
	if result.Total != 4 {
		t.Errorf("expected total 4, got %d", result.Total)
	}

	// Other should be 1 (cherry was trimmed)
	if result.Other != 1 {
		t.Errorf("expected other 1, got %d", result.Other)
	}
}
