package ldclient

import (
	"errors"
	"fmt"
	"testing"

	"github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
	"github.com/launchdarkly/go-server-sdk/v6/internal/bigsegments"
	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"

	"github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
	"github.com/launchdarkly/go-server-sdk/v6/subsystems"
	"github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoreimpl"
	"github.com/launchdarkly/go-server-sdk/v6/testhelpers/ldtestdata"

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

const bigSegmentKey = "segmentkey"

// The definition of this function has to be kept in sync with the equivalent function in
// go-server-sdk-evaluation.
func makeBigSegmentRef(segmentKey string, generation int) string {
	return fmt.Sprintf("%s.g%d", segmentKey, generation)
}

func addBigSegmentAndFlag(testData *ldtestdata.TestDataSource) {
	segment := ldbuilders.NewSegmentBuilder(bigSegmentKey).
		Unbounded(true).
		Generation(1).
		Build()
	flag := ldbuilders.NewFlagBuilder(evalFlagKey).On(true).
		Variations(ldvalue.Bool(false), ldvalue.Bool(true)).
		FallthroughVariation(0).
		AddRule(ldbuilders.NewRuleBuilder().Variation(1).Clauses(
			ldbuilders.Clause("", ldmodel.OperatorSegmentMatch, ldvalue.String(segment.Key)),
		)).
		Build()
	testData.UsePreconfiguredSegment(segment)
	testData.UsePreconfiguredFlag(flag)
}

func doBigSegmentsTest(
	t *testing.T,
	action func(client *LDClient, bsStore *mocks.MockBigSegmentStore),
) {
	mockLog := ldlogtest.NewMockLog()
	defer mockLog.DumpIfTestFailed(t)
	testData := ldtestdata.DataSource()
	bsStore := &mocks.MockBigSegmentStore{}
	bsStore.TestSetMetadataToCurrentTime()

	addBigSegmentAndFlag(testData)

	client := makeTestClientWithConfig(func(c *Config) {
		c.DataSource = testData
		c.BigSegments = ldcomponents.BigSegments(
			mocks.SingleComponentConfigurer[subsystems.BigSegmentStore]{Instance: bsStore},
		)
		c.Logging = ldcomponents.Logging().Loggers(mockLog.Loggers)
	})
	defer client.Close()

	action(client, bsStore)
}

func TestEvalWithBigSegments(t *testing.T) {
	t.Run("user not found", func(t *testing.T) {
		doBigSegmentsTest(t, func(client *LDClient, bsStore *mocks.MockBigSegmentStore) {
			value, detail, err := client.BoolVariationDetail(evalFlagKey, evalTestUser, false)
			require.NoError(t, err)
			assert.False(t, value)
			assert.Equal(t, ldreason.BigSegmentsHealthy, detail.Reason.GetBigSegmentsStatus())
		})
	})

	t.Run("user found", func(t *testing.T) {
		doBigSegmentsTest(t, func(client *LDClient, bsStore *mocks.MockBigSegmentStore) {
			membership := ldstoreimpl.NewBigSegmentMembershipFromSegmentRefs(
				[]string{makeBigSegmentRef(bigSegmentKey, 1)}, nil)
			bsStore.TestSetMembership(bigsegments.HashForContextKey(evalTestUser.Key()), membership)

			value, detail, err := client.BoolVariationDetail(evalFlagKey, evalTestUser, false)
			require.NoError(t, err)
			assert.True(t, value)
			assert.Equal(t, ldreason.BigSegmentsHealthy, detail.Reason.GetBigSegmentsStatus())
		})
	})

	t.Run("store error", func(t *testing.T) {
		doBigSegmentsTest(t, func(client *LDClient, bsStore *mocks.MockBigSegmentStore) {
			bsStore.TestSetMembershipError(errors.New("sorry"))

			value, detail, err := client.BoolVariationDetail(evalFlagKey, evalTestUser, false)
			require.NoError(t, err)
			assert.False(t, value)
			assert.Equal(t, ldreason.BigSegmentsStoreError, detail.Reason.GetBigSegmentsStatus())
		})
	})

	t.Run("store not configured", func(t *testing.T) {
		// deliberately not using a configuration with a Big Segment store here
		withClientEvalTestParams(func(p clientEvalTestParams) {
			addBigSegmentAndFlag(p.data)

			value, detail, err := p.client.BoolVariationDetail(evalFlagKey, evalTestUser, false)
			require.NoError(t, err)
			assert.False(t, value)
			assert.Equal(t, ldreason.BigSegmentsNotConfigured, detail.Reason.GetBigSegmentsStatus())
		})
	})
}
