package featuregate

import (
	"context"
	"testing"
	"time"

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

	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestFeatureGateImp_CheckFeatureEnabled(t *testing.T) {
	featureMap := map[string]FeatureGate{
		"test_feature": {
			DisabledTenants:        []uint64{1},
			DisabledProjects:       []uint64{10},
			DisabledCloudProviders: []string{"AWS"},
			DisabledRegions:        []string{"us-east-1"},
			EnabledTenants:         []uint64{2},
			EnabledProjects:        []uint64{20},
			EnabledCloudProviders:  []string{"GCP"},
			EnabledRegions:         []string{"us-west-1"},
			All:                    false,
		},
		"all_true_feature": {
			All: true,
		},
	}
	fg := NewFeatureGate(featureMap, nil)

	t.Run("feature not found", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("not_found", 99, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
	})

	t.Run("disabled by tenant", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 1, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
	})

	t.Run("disabled by project", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 10, "AZURE", "eu-central-1")
		assert.False(t, enabled)
	})

	t.Run("disabled by cloud provider", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 99, "AWS", "eu-central-1")
		assert.False(t, enabled)
	})

	t.Run("disabled by region", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 99, "AZURE", "us-east-1")
		assert.False(t, enabled)
	})

	t.Run("enabled by tenant", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 2, 99, "AZURE", "eu-central-1")
		assert.True(t, enabled)
	})

	t.Run("enabled by project", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 20, "AZURE", "eu-central-1")
		assert.True(t, enabled)
	})

	t.Run("enabled by cloud provider", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 99, "GCP", "eu-central-1")
		assert.True(t, enabled)
	})

	t.Run("enabled by region", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 99, "AZURE", "us-west-1")
		assert.True(t, enabled)
	})

	t.Run("no match and all is false", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("test_feature", 99, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
	})

	t.Run("no match and all is true", func(t *testing.T) {
		enabled := fg.CheckFeatureEnabled("all_true_feature", 99, 99, "AZURE", "eu-central-1")
		assert.True(t, enabled)
	})
}

func TestFeatureGateImp_CheckFeatureEnabledV2(t *testing.T) {
	pastTime := time.Now().Add(-time.Hour).Format(time.RFC3339)
	futureTime := time.Now().Add(time.Hour).Format(time.RFC3339)

	featureMap := map[string]FeatureGate{
		"test_feature": {
			DisabledTenants:        []uint64{1},
			DisabledProjects:       []uint64{10},
			DisabledCloudProviders: []string{"AWS"},
			DisabledRegions:        []string{"us-east-1"},
			EnabledTenants:         []uint64{2},
			EnabledProjects:        []uint64{20},
			EnabledCloudProviders:  []string{"GCP"},
			EnabledRegions:         []string{"us-west-1"},
			All:                    false,
		},
		"all_true_feature": {
			All: true,
		},
		"org_after_feature": {
			AffectOrgCreatedAfter: pastTime,
			All:                   false,
		},
		"org_after_feature_future": {
			AffectOrgCreatedAfter: futureTime,
			All:                   false,
		},
		"org_after_feature_invalid_time": {
			AffectOrgCreatedAfter: "invalid-time",
			All:                   false,
		},
	}

	mockOrgCreateTimeFetcher := func(ctx context.Context, orgId uint64) (time.Time, bizerr.BizError) {
		if orgId == 100 { // org created before AffectOrgCreatedAfter
			return time.Now().Add(-2 * time.Hour), nil
		}
		if orgId == 200 { // org created after AffectOrgCreatedAfter
			return time.Now(), nil
		}
		if orgId == 300 { // fetcher error
			return time.Time{}, bizerr.New(bizerr.ErrCodeUnknown, "fetcher error")
		}
		return time.Time{}, bizerr.New(bizerr.ErrCodeUnknown, "org not found")
	}

	fg := NewFeatureGate(featureMap, mockOrgCreateTimeFetcher)
	fgNoFetcher := NewFeatureGate(featureMap, nil)

	t.Run("feature not found", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "not_found", 99, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("disabled by tenant", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "test_feature", 1, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("enabled by project", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "test_feature", 99, 20, "AZURE", "eu-central-1")
		assert.True(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("no match and all is true", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "all_true_feature", 99, 99, "AZURE", "eu-central-1")
		assert.True(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("org created after", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "org_after_feature", 200, 99, "AZURE", "eu-central-1")
		assert.True(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("org created before", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "org_after_feature", 100, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("org after feature with future time", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(
			t.Context(),
			"org_after_feature_future",
			200,
			99,
			"AZURE",
			"eu-central-1",
		)
		assert.False(t, enabled)
		assert.NoError(t, err)
	})

	t.Run("org after feature with invalid time format", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(
			t.Context(),
			"org_after_feature_invalid_time",
			200,
			99,
			"AZURE",
			"eu-central-1",
		)
		assert.False(t, enabled)
		assert.Error(t, err)
	})

	t.Run("org create time fetcher is nil", func(t *testing.T) {
		enabled, err := fgNoFetcher.CheckFeatureEnabledV2(
			t.Context(),
			"org_after_feature",
			200,
			99,
			"AZURE",
			"eu-central-1",
		)
		assert.False(t, enabled)
		require.Error(t, err)
		assert.Equal(t, bizerr.ErrCodeUnknown, err.ErrCode())
	})

	t.Run("org create time fetcher returns error", func(t *testing.T) {
		enabled, err := fg.CheckFeatureEnabledV2(t.Context(), "org_after_feature", 300, 99, "AZURE", "eu-central-1")
		assert.False(t, enabled)
		require.Error(t, err)
		assert.Equal(t, bizerr.ErrCodeUnknown, err.ErrCode())
	})
}
