package regexp

import (
	"errors"
	"testing"

	"github.com/betterleaks/betterleaks/regexp/internal"
)

type countingEngine struct {
	compiles int
}

func (e *countingEngine) Compile(str string) (internal.CompiledRegexp, error) {
	e.compiles++
	return Stdlib{}.Compile(str)
}

func (e *countingEngine) Version() string { return "counting" }

type failingEngine struct{}

func (e failingEngine) Compile(string) (internal.CompiledRegexp, error) {
	return nil, errors.New("compile failed")
}

func (e failingEngine) Version() string { return "failing" }

func TestCompileIsLazy(t *testing.T) {
	previous := currentEngine
	defer SetEngine(previous)

	engine := &countingEngine{}
	SetEngine(engine)

	re, err := Compile(`(foo)(bar)?`)
	if err != nil {
		t.Fatal(err)
	}
	if engine.compiles != 0 {
		t.Fatalf("Compile compiled regex eagerly")
	}
	if re.String() != `(foo)(bar)?` {
		t.Fatalf("String changed pattern: %q", re.String())
	}
	if re.NumSubexp() != 2 {
		t.Fatalf("NumSubexp = %d, want 2", re.NumSubexp())
	}
	if engine.compiles != 0 {
		t.Fatalf("metadata access compiled regex eagerly")
	}

	if !re.MatchString("foobar") {
		t.Fatal("MatchString returned false")
	}
	if engine.compiles != 1 {
		t.Fatalf("compiles = %d, want 1", engine.compiles)
	}
	_ = re.FindString("foobar")
	if engine.compiles != 1 {
		t.Fatalf("regex compiled more than once")
	}
}

func TestLazyCompileFailureDoesNotPanic(t *testing.T) {
	previous := currentEngine
	defer SetEngine(previous)

	SetEngine(failingEngine{})

	re, err := Compile(`foo`)
	if err != nil {
		t.Fatal(err)
	}
	if re.MatchString("foo") {
		t.Fatal("MatchString returned true after compile failure")
	}
	if got := re.FindString("foo"); got != "" {
		t.Fatalf("FindString = %q, want empty", got)
	}
	if got := re.FindStringSubmatch("foo"); got != nil {
		t.Fatalf("FindStringSubmatch = %#v, want nil", got)
	}
	if got := re.FindAllStringIndex("foo", -1); got != nil {
		t.Fatalf("FindAllStringIndex = %#v, want nil", got)
	}
	if got := re.ReplaceAllString("foo", "bar"); got != "foo" {
		t.Fatalf("ReplaceAllString = %q, want original", got)
	}
	if got := re.SubexpNames(); got != nil {
		t.Fatalf("SubexpNames = %#v, want nil", got)
	}
}

func TestCompileReturnsLazyCompileError(t *testing.T) {
	previous := currentEngine
	defer SetEngine(previous)

	SetEngine(failingEngine{})

	re, err := Compile(`foo`)
	if err != nil {
		t.Fatal(err)
	}
	if err := re.Compile(); err == nil {
		t.Fatal("Compile returned nil after engine compile failure")
	}
}
