package repoext

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
)

func TestKeyspaceFetcher_GetKeyspace(t *testing.T) {
	ctx := context.Background()

	t.Run("successfully fetches keyspace", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		tidbGroups := []*entity.TiDBGroup{{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks1"}}}
		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, []string{"c1"}, mock.Anything, true).
			Return(tidbGroups, nil)

		keyspace, err := fetcher.GetKeyspace(ctx, "c1", "sp1")

		require.NoError(t, err)
		assert.Equal(t, "ks1", keyspace)
		mockRepo.AssertExpectations(t)
	})

	t.Run("returns error if no TiDBGroup found", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, []string{"c1"}, mock.Anything, true).
			Return([]*entity.TiDBGroup{}, nil)

		keyspace, err := fetcher.GetKeyspace(ctx, "c1", "sp1")

		require.Error(t, err)
		assert.Empty(t, keyspace)
		require.ErrorIs(t, err, TiDBGroupNotFoundErr)
	})

	t.Run("returns error if repo returns error", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		mockErr := fmt.Errorf("db failure")
		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, []string{"c1"}, mock.Anything, true).
			Return(nil, mockErr)

		keyspace, err := fetcher.GetKeyspace(ctx, "c1", "sp1")

		require.Error(t, err)
		assert.Empty(t, keyspace)
		require.ErrorIs(t, err, mockErr)
	})

	t.Run("returns error if multiple keyspaces mismatch", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		tidbGroups := []*entity.TiDBGroup{
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks1"}},
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks2"}},
		}
		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, []string{"c1"}, mock.Anything, true).
			Return(tidbGroups, nil)

		keyspace, err := fetcher.GetKeyspace(ctx, "c1", "sp1")

		require.Error(t, err)
		assert.Empty(t, keyspace)
		require.ErrorIs(t, err, DifferentKeyspaceErr)
	})
}

func TestKeyspaceFetcher_GetClusterIds(t *testing.T) {
	ctx := context.Background()

	t.Run("successfully fetches cluster ids", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		tidbGroups := []*entity.TiDBGroup{
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks1", Id: "g1"}},
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks1", Id: "g2"}},
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks2", Id: "g3"}},
			{TiDBGroup: apiv1.TiDBGroup{ClusterId: "ks2", Id: "g4"}},
		}

		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, mock.Anything, []string{"ks1", "ks2"}, true).
			Return(tidbGroups, nil)

		clusterIds, err := fetcher.GetClusterIds(ctx, "sp1", []string{"ks1", "ks2"})

		require.NoError(t, err)
		assert.Equal(t, map[string]string{
			"ks1": "ks1",
			"ks2": "ks2",
		}, clusterIds)
		mockRepo.AssertExpectations(t)
	})

	t.Run("returns error if no TiDBGroup found", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, mock.Anything, []string{"ks1"}, true).
			Return([]*entity.TiDBGroup{}, nil)

		clusterIds, err := fetcher.GetClusterIds(ctx, "sp1", []string{"ks1"})

		require.Error(t, err)
		assert.Nil(t, clusterIds)
		require.ErrorIs(t, err, TiDBGroupNotFoundErr)
	})

	t.Run("returns error if repo returns error", func(t *testing.T) {
		mockRepo := mocks.NewTiDBGroupRepo(t)
		fetcher := NewKeyspaceFetcher(mockRepo)

		mockErr := fmt.Errorf("db failure")
		mockRepo.On("List", mock.Anything, []string{"sp1"}, mock.Anything, mock.Anything, []string{"ks1"}, true).
			Return(nil, mockErr)

		clusterIds, err := fetcher.GetClusterIds(ctx, "sp1", []string{"ks1"})

		require.Error(t, err)
		assert.Nil(t, clusterIds)
		require.ErrorIs(t, err, mockErr)
	})
}
