// Licensed to the LF AI & Data foundation under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package datacoord

import (
	"context"
	"testing"

	"github.com/cockroachdb/errors"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"

	"github.com/milvus-io/milvus/internal/metastore/mocks"
	"github.com/milvus-io/milvus/pkg/v2/proto/indexpb"
	"github.com/milvus-io/milvus/pkg/v2/proto/workerpb"
)

type AnalyzeMetaSuite struct {
	suite.Suite

	collectionID int64
	partitionID  int64
	fieldID      int64
	segmentIDs   []int64
}

func (s *AnalyzeMetaSuite) initParams() {
	s.collectionID = 100
	s.partitionID = 101
	s.fieldID = 102
	s.segmentIDs = []int64{1000, 1001, 1002, 1003}
}

func (s *AnalyzeMetaSuite) Test_AnalyzeMeta() {
	s.initParams()

	catalog := mocks.NewDataCoordCatalog(s.T())
	catalog.EXPECT().ListAnalyzeTasks(mock.Anything).Return([]*indexpb.AnalyzeTask{
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       1,
			State:        indexpb.JobState_JobStateNone,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       2,
			State:        indexpb.JobState_JobStateInit,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       3,
			State:        indexpb.JobState_JobStateInProgress,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       4,
			State:        indexpb.JobState_JobStateRetry,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       5,
			State:        indexpb.JobState_JobStateFinished,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       6,
			State:        indexpb.JobState_JobStateFailed,
		},
	}, nil)

	catalog.EXPECT().SaveAnalyzeTask(mock.Anything, mock.Anything).Return(nil)
	catalog.EXPECT().DropAnalyzeTask(mock.Anything, mock.Anything).Return(nil)

	ctx := context.Background()

	am, err := newAnalyzeMeta(ctx, catalog)
	s.NoError(err)
	s.Equal(6, len(am.GetAllTasks()))

	s.Run("GetTask", func() {
		t := am.GetTask(1)
		s.NotNil(t)

		t = am.GetTask(100)
		s.Nil(t)
	})

	s.Run("AddAnalyzeTask", func() {
		t := &indexpb.AnalyzeTask{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       7,
		}

		err := am.AddAnalyzeTask(t)
		s.NoError(err)
		s.Equal(7, len(am.GetAllTasks()))

		err = am.AddAnalyzeTask(t)
		s.NoError(err)
		s.Equal(7, len(am.GetAllTasks()))
	})

	s.Run("DropAnalyzeTask", func() {
		err := am.DropAnalyzeTask(ctx, 7)
		s.NoError(err)
		s.Equal(6, len(am.GetAllTasks()))
	})

	s.Run("UpdateVersion", func() {
		err := am.UpdateVersion(1, 1)
		s.NoError(err)
		s.Equal(int64(1), am.GetTask(1).Version)
	})

	s.Run("BuildingTask", func() {
		err := am.BuildingTask(1)
		s.NoError(err)
		s.Equal(indexpb.JobState_JobStateInProgress, am.GetTask(1).State)
	})

	s.Run("FinishTask", func() {
		err := am.FinishTask(1, &workerpb.AnalyzeResult{
			TaskID: 1,
			State:  indexpb.JobState_JobStateFinished,
		})
		s.NoError(err)
		s.Equal(indexpb.JobState_JobStateFinished, am.GetTask(1).State)
	})
}

func (s *AnalyzeMetaSuite) Test_failCase() {
	s.initParams()

	catalog := mocks.NewDataCoordCatalog(s.T())
	catalog.EXPECT().ListAnalyzeTasks(mock.Anything).Return(nil, errors.New("error")).Once()
	ctx := context.Background()
	am, err := newAnalyzeMeta(ctx, catalog)
	s.Error(err)
	s.Nil(am)

	catalog.EXPECT().ListAnalyzeTasks(mock.Anything).Return([]*indexpb.AnalyzeTask{
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       1,
			State:        indexpb.JobState_JobStateInit,
		},
		{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       2,
			State:        indexpb.JobState_JobStateFinished,
		},
	}, nil)
	am, err = newAnalyzeMeta(ctx, catalog)
	s.NoError(err)
	s.NotNil(am)
	s.Equal(2, len(am.GetAllTasks()))

	catalog.EXPECT().SaveAnalyzeTask(mock.Anything, mock.Anything).Return(errors.New("error"))
	catalog.EXPECT().DropAnalyzeTask(mock.Anything, mock.Anything).Return(errors.New("error"))
	s.Run("AddAnalyzeTask", func() {
		t := &indexpb.AnalyzeTask{
			CollectionID: s.collectionID,
			PartitionID:  s.partitionID,
			FieldID:      s.fieldID,
			SegmentIDs:   s.segmentIDs,
			TaskID:       1111,
		}
		err := am.AddAnalyzeTask(t)
		s.Error(err)
		s.Nil(am.GetTask(1111))
	})

	s.Run("DropAnalyzeTask", func() {
		err := am.DropAnalyzeTask(ctx, 1)
		s.Error(err)
		s.NotNil(am.GetTask(1))
	})

	s.Run("UpdateVersion", func() {
		err := am.UpdateVersion(777, 1)
		s.Error(err)

		err = am.UpdateVersion(1, 1)
		s.Error(err)
		s.Equal(int64(0), am.GetTask(1).Version)
	})

	s.Run("BuildingTask", func() {
		err := am.BuildingTask(777)
		s.Error(err)

		err = am.BuildingTask(1)
		s.Error(err)
		s.Equal(int64(0), am.GetTask(1).NodeID)
		s.Equal(indexpb.JobState_JobStateInit, am.GetTask(1).State)
	})

	s.Run("FinishTask", func() {
		err := am.FinishTask(777, nil)
		s.Error(err)

		err = am.FinishTask(1, &workerpb.AnalyzeResult{
			TaskID: 1,
			State:  indexpb.JobState_JobStateFinished,
		})
		s.Error(err)
		s.Equal(indexpb.JobState_JobStateInit, am.GetTask(1).State)
	})

	s.Run("CheckCleanAnalyzeTask", func() {
		canRecycle, t := am.CheckCleanAnalyzeTask(1)
		s.False(canRecycle)
		s.Equal(indexpb.JobState_JobStateInit, t.GetState())

		canRecycle, t = am.CheckCleanAnalyzeTask(777)
		s.True(canRecycle)
		s.Nil(t)

		canRecycle, t = am.CheckCleanAnalyzeTask(2)
		s.True(canRecycle)
		s.Equal(indexpb.JobState_JobStateFinished, t.GetState())
	})
}

func TestAnalyzeMeta(t *testing.T) {
	suite.Run(t, new(AnalyzeMetaSuite))
}
