package evaluation

import (
	"testing"

	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"

	"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"

	m "github.com/launchdarkly/go-test-helpers/v3/matchers"
)

func TestFlagMatchesContextFromTargets(t *testing.T) {
	variations := []ldvalue.Value{ldvalue.String("fall"), ldvalue.String("match1"), ldvalue.String("match2"), ldvalue.String("off")}
	nonMatchVar, matchVar1, matchVar2, offVar := 0, 1, 2, 3

	makeBaseFlag := func() ldmodel.FeatureFlag {
		return ldbuilders.NewFlagBuilder("flagkey").
			Variations(variations...).On(true).OffVariation(offVar).FallthroughVariation(nonMatchVar).Build()
	}
	user := func(key string) ldcontext.Context { return ldcontext.New(key) }
	dog := func(key string) ldcontext.Context { return ldcontext.NewWithKind("dog", key) }

	assertResult := func(t *testing.T, flag ldmodel.FeatureFlag, context ldcontext.Context, expectedVariation int) {
		t.Helper()
		result := basicEvaluator().Evaluate(&flag, context, FailOnAnyPrereqEvent(t))
		expectedReason := ldreason.NewEvalReasonTargetMatch()
		if expectedVariation == nonMatchVar {
			expectedReason = ldreason.NewEvalReasonFallthrough()
		}
		m.In(t).Assert(result, ResultDetailProps(expectedVariation, variations[expectedVariation], expectedReason))
	}

	t.Run("flag has Targets only", func(t *testing.T) {
		flag := makeBaseFlag()
		flag.Targets = []ldmodel.Target{ // these targets are for the "user" kind only
			{Variation: matchVar1, Values: []string{"c"}},
			{Variation: matchVar2, Values: []string{"b", "a"}},
		}
		ldmodel.PreprocessFlag(&flag)

		assertResult(t, flag, user("a"), matchVar2)
		assertResult(t, flag, user("b"), matchVar2)
		assertResult(t, flag, user("c"), matchVar1)
		assertResult(t, flag, user("z"), nonMatchVar)

		assertResult(t, flag, ldcontext.NewMulti(dog("b"), user("a")), matchVar2)
		assertResult(t, flag, ldcontext.NewMulti(dog("a"), user("c")), matchVar1)
		assertResult(t, flag, ldcontext.NewMulti(dog("a"), user("z")), nonMatchVar)
		assertResult(t, flag, ldcontext.NewMulti(dog("a"), ldcontext.NewWithKind("cat", "b")), nonMatchVar)
	})

	t.Run("flag has Targets+ContextTargets", func(t *testing.T) {
		flag := makeBaseFlag()
		flag.Targets = []ldmodel.Target{
			{Variation: matchVar1, Values: []string{"c"}},
			{Variation: matchVar2, Values: []string{"b", "a"}},
		}
		flag.ContextTargets = []ldmodel.Target{
			{ContextKind: "dog", Variation: matchVar1, Values: []string{"a", "b"}},
			{ContextKind: "dog", Variation: matchVar2, Values: []string{"c"}},
			{ContextKind: "user", Variation: matchVar1},
			{ContextKind: "user", Variation: matchVar2},
		}
		ldmodel.PreprocessFlag(&flag)

		assertResult(t, flag, user("a"), matchVar2)
		assertResult(t, flag, user("b"), matchVar2)
		assertResult(t, flag, user("c"), matchVar1)
		assertResult(t, flag, user("z"), nonMatchVar)

		assertResult(t, flag, ldcontext.NewMulti(dog("b"), user("a")), matchVar1)   // the "dog" target takes precedence due to ordering
		assertResult(t, flag, ldcontext.NewMulti(dog("z"), user("a")), matchVar2)   // "dog" targets don't match, continue to "user" targets
		assertResult(t, flag, ldcontext.NewMulti(dog("x"), user("z")), nonMatchVar) // nothing matches
		assertResult(t, flag, ldcontext.NewMulti(dog("a"), ldcontext.NewWithKind("cat", "b")), matchVar1)
	})

}
