package ldmodel

import (
	"fmt"
	"testing"

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

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

func TestClauseFindValue(t *testing.T) {
	foundValues := []ldvalue.Value{ldvalue.Bool(true), ldvalue.Int(2), ldvalue.String("x")}
	notFoundValues := []ldvalue.Value{ldvalue.Bool(false), ldvalue.Int(3), ldvalue.String("y")}

	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			clause := Clause{Op: OperatorIn, Values: foundValues}
			if withPreprocessing {
				clause.preprocessed = preprocessClause(clause)
			}
			for _, value := range foundValues {
				assert.True(t, EvaluatorAccessors.ClauseFindValue(&clause, value), "value: %s", value)
			}
			for _, value := range notFoundValues {
				assert.False(t, EvaluatorAccessors.ClauseFindValue(&clause, value), "value: %s", value)
			}
		})
	}

	t.Run("unsupported value types return false", func(t *testing.T) {
		badValues := []ldvalue.Value{ldvalue.Null(), ldvalue.ArrayOf(), ldvalue.ObjectBuild().Build()}
		for _, withPreprocessing := range []bool{false, true} {
			t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
				clause := Clause{Op: OperatorIn, Values: badValues}
				if withPreprocessing {
					clause.preprocessed = preprocessClause(clause)
				}
				for _, value := range badValues {
					assert.False(t, EvaluatorAccessors.ClauseFindValue(&clause, value), "value: %s", value)
				}
			})
		}
	})

	t.Run("nil pointer", func(t *testing.T) {
		assert.False(t, EvaluatorAccessors.ClauseFindValue(nil, ldvalue.String("")))
	})
}

func TestClauseGetValueAsRegexp(t *testing.T) {
	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			clause := Clause{Op: OperatorMatches,
				Values: []ldvalue.Value{ldvalue.String("a.*b"), ldvalue.String("**"), ldvalue.Int(1)}}
			if withPreprocessing {
				clause.preprocessed = preprocessClause(clause)
			}

			r := EvaluatorAccessors.ClauseGetValueAsRegexp(&clause, 0)
			if assert.NotNil(t, r) {
				assert.Equal(t, "a.*b", r.String())
			}

			assert.Nil(t, EvaluatorAccessors.ClauseGetValueAsRegexp(&clause, 1))
			assert.Nil(t, EvaluatorAccessors.ClauseGetValueAsRegexp(&clause, 2))

			assert.Nil(t, EvaluatorAccessors.ClauseGetValueAsRegexp(&clause, -1)) // out of range
			assert.Nil(t, EvaluatorAccessors.ClauseGetValueAsRegexp(&clause, 3))  // out of range
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		assert.Nil(t, EvaluatorAccessors.ClauseGetValueAsRegexp(nil, 0))
	})
}

func TestClauseGetValueAsSemanticVersion(t *testing.T) {
	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			clause := Clause{Op: OperatorSemVerEqual,
				Values: []ldvalue.Value{ldvalue.String("1.2.3"), ldvalue.Int(100000)}}
			if withPreprocessing {
				clause.preprocessed = preprocessClause(clause)
			}

			result, ok := EvaluatorAccessors.ClauseGetValueAsSemanticVersion(&clause, 0)
			assert.True(t, ok)
			expected, _ := TypeConversions.ValueToSemanticVersion(clause.Values[0])
			assert.Equal(t, expected, result)

			_, ok = EvaluatorAccessors.ClauseGetValueAsSemanticVersion(&clause, 1)
			assert.False(t, ok)

			_, ok = EvaluatorAccessors.ClauseGetValueAsSemanticVersion(&clause, -1) // out of range
			assert.False(t, ok)
			_, ok = EvaluatorAccessors.ClauseGetValueAsSemanticVersion(&clause, 2) // out of range
			assert.False(t, ok)
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		_, ok := EvaluatorAccessors.ClauseGetValueAsSemanticVersion(nil, 0)
		assert.False(t, ok)
	})
}

func TestClauseGetValueAsTimestamp(t *testing.T) {
	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			clause := Clause{Op: OperatorBefore,
				Values: []ldvalue.Value{ldvalue.String("1970-01-01T00:00:00Z"), ldvalue.Int(100000), ldvalue.Bool(true)}}
			if withPreprocessing {
				clause.preprocessed = preprocessClause(clause)
			}

			result, ok := EvaluatorAccessors.ClauseGetValueAsTimestamp(&clause, 0)
			assert.True(t, ok)
			expected, _ := TypeConversions.ValueToTimestamp(clause.Values[0])
			assert.Equal(t, expected, result)

			result, ok = EvaluatorAccessors.ClauseGetValueAsTimestamp(&clause, 1)
			assert.True(t, ok)
			expected, _ = TypeConversions.ValueToTimestamp(clause.Values[1])
			assert.Equal(t, expected, result)

			_, ok = EvaluatorAccessors.ClauseGetValueAsTimestamp(&clause, 2)
			assert.False(t, ok)

			_, ok = EvaluatorAccessors.ClauseGetValueAsTimestamp(&clause, -1) // out of range
			assert.False(t, ok)
			_, ok = EvaluatorAccessors.ClauseGetValueAsTimestamp(&clause, 3) // out of range
			assert.False(t, ok)
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		_, ok := EvaluatorAccessors.ClauseGetValueAsTimestamp(nil, 0)
		assert.False(t, ok)
	})
}

func TestSegmentFindKeyInExcluded(t *testing.T) {
	foundValues := []string{"a", "b", "c"}
	notFoundValues := []string{"d", "e", "f"}

	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			segment := Segment{Excluded: foundValues}
			if withPreprocessing {
				PreprocessSegment(&segment)
			}
			for _, value := range foundValues {
				assert.True(t, EvaluatorAccessors.SegmentFindKeyInExcluded(&segment, value), value)
			}
			for _, value := range notFoundValues {
				assert.False(t, EvaluatorAccessors.SegmentFindKeyInExcluded(&segment, value), value)
			}
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		assert.False(t, EvaluatorAccessors.SegmentFindKeyInExcluded(nil, ""))
	})
}

func TestSegmentFindValueInIncluded(t *testing.T) {
	foundValues := []string{"a", "b", "c"}
	notFoundValues := []string{"d", "e", "f"}

	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			segment := Segment{Included: foundValues}
			if withPreprocessing {
				PreprocessSegment(&segment)
			}
			for _, value := range foundValues {
				assert.True(t, EvaluatorAccessors.SegmentFindKeyInIncluded(&segment, value), value)
			}
			for _, value := range notFoundValues {
				assert.False(t, EvaluatorAccessors.SegmentFindKeyInIncluded(&segment, value), value)
			}
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		assert.False(t, EvaluatorAccessors.SegmentFindKeyInIncluded(nil, ""))
	})
}

func TestSegmentTargetFindKey(t *testing.T) {
	foundValues := []string{"a", "b", "c"}
	notFoundValues := []string{"d", "e", "f"}

	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			target := SegmentTarget{Values: foundValues}
			if withPreprocessing {
				target.preprocessed.valuesMap = preprocessStringSet(target.Values)
			}
			for _, value := range foundValues {
				assert.True(t, EvaluatorAccessors.SegmentTargetFindKey(&target, value), value)
			}
			for _, value := range notFoundValues {
				assert.False(t, EvaluatorAccessors.SegmentTargetFindKey(&target, value), value)
			}
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		assert.False(t, EvaluatorAccessors.SegmentTargetFindKey(nil, ""))
	})
}

func TestTargetFindKey(t *testing.T) {
	foundValues := []string{"a", "b", "c"}
	notFoundValues := []string{"d", "e", "f"}

	for _, withPreprocessing := range []bool{false, true} {
		t.Run(fmt.Sprintf("preprocessed: %t", withPreprocessing), func(t *testing.T) {
			target := Target{Values: foundValues}
			if withPreprocessing {
				target.preprocessed.valuesMap = preprocessStringSet(target.Values)
			}
			for _, value := range foundValues {
				assert.True(t, EvaluatorAccessors.TargetFindKey(&target, value), value)
			}
			for _, value := range notFoundValues {
				assert.False(t, EvaluatorAccessors.TargetFindKey(&target, value), value)
			}
		})
	}

	t.Run("nil pointer", func(t *testing.T) {
		assert.False(t, EvaluatorAccessors.TargetFindKey(nil, ""))
	})
}
