package globalservice

import (
	"bytes"
	"context"
	"fmt"
	"testing"

	s3api "github.com/aws/aws-sdk-go-v2/service/s3"

	"github.com/stretchr/testify/assert"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/metamanager/s3"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
	mock_meta_manager "github.com/tidbcloud/observability/test/mockmetamanager"
	"github.com/tidbcloud/observability/test/mockresourcemanager"
	"go.uber.org/mock/gomock"
	batchv1 "k8s.io/api/batch/v1"
	"k8s.io/utils/ptr"
)

func TestEvaluationHandler_CreateEvaluation(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	jobRW := mockresourcemanager.NewMockJobReaderWriter(ctrl)
	resourcemanager.MockJobReaderWriter(jobRW)

	meta := mock_meta_manager.NewMockEvaluationMeta(ctrl)
	s3.Mock(meta)

	t.Run("save meta error ", func(t *testing.T) {
		meta.EXPECT().SaveMeta(context.TODO(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "")).Times(1)
		_, err := GetEvaluationHandler().CreateEvaluation(context.TODO(), message.CreateEvaluationReq{})
		assert.Error(t, err)
	})

	t.Run("create job error ", func(t *testing.T) {
		meta.EXPECT().SaveMeta(context.TODO(), gomock.Any()).Return(nil).Times(1)
		jobRW.EXPECT().Create(context.TODO(), gomock.Any()).Return(errors.New(errors.UnrecognizedError, "")).Times(1)
		_, err := GetEvaluationHandler().CreateEvaluation(context.TODO(), message.CreateEvaluationReq{
			Name: "test",
		})
		assert.Error(t, err)
	})

	t.Run("normal", func(t *testing.T) {
		meta.EXPECT().SaveMeta(context.TODO(), gomock.Any()).Return(nil).Times(1)
		jobRW.EXPECT().Create(context.TODO(), gomock.Any()).Return(nil).Times(1)
		resp, err := GetEvaluationHandler().CreateEvaluation(context.TODO(), message.CreateEvaluationReq{
			Name: "test",
			DatasetRange: message.DatasetRange{
				DatasetCategory: []constants.DatasetCategory{
					constants.Benchmark,
					constants.Baseline,
					constants.Question,
				},
			},
		})
		assert.NoError(t, err)
		assert.Equal(t, "test", resp.Name)
		assert.NotEmpty(t, resp.ID)
	})
}
func TestEvaluationHandler_GetEvaluation(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	jobRW := mockresourcemanager.NewMockJobReaderWriter(ctrl)
	resourcemanager.MockJobReaderWriter(jobRW)

	meta := mock_meta_manager.NewMockEvaluationMeta(ctrl)
	s3.Mock(meta)

	t.Run("meta not found", func(t *testing.T) {
		meta.EXPECT().GetMeta(context.TODO(), gomock.Any(), gomock.Any()).Return(structs.Evaluation{}, errors.New(errors.EvaluationNotFound, "")).Times(1)
		_, err := GetEvaluationHandler().GetEvaluation(context.TODO(), message.GetEvaluationReq{})
		assert.Error(t, err)
	})
	t.Run("job not found", func(t *testing.T) {
		meta.EXPECT().GetMeta(context.TODO(), gomock.Any(), gomock.Any()).Return(structs.Evaluation{}, nil).Times(1)
		jobRW.EXPECT().Get(context.TODO(), gomock.Any(), gomock.Any()).Return(nil, errors.New(errors.EvaluationNotFound, "")).Times(1)
		_, err := GetEvaluationHandler().GetEvaluation(context.TODO(), message.GetEvaluationReq{})
		assert.Error(t, err)
	})

	t.Run("EvaluationEvaluating", func(t *testing.T) {
		meta.EXPECT().GetMeta(context.TODO(), gomock.Any(), gomock.Any()).Return(structs.Evaluation{}, nil).Times(1)
		jobRW.EXPECT().Get(context.TODO(), gomock.Any(), gomock.Any()).Return(&batchv1.Job{
			Status: batchv1.JobStatus{
				Succeeded: 0,
				Failed:    0,
			},
		}, nil).Times(1)
		eva, err := GetEvaluationHandler().GetEvaluation(context.TODO(), message.GetEvaluationReq{})
		assert.NoError(t, err)
		assert.Equal(t, constants.EvaluationEvaluating, eva.Phase)
	})

	t.Run("EvaluationFailed", func(t *testing.T) {
		meta.EXPECT().GetMeta(context.TODO(), gomock.Any(), gomock.Any()).Return(structs.Evaluation{}, nil).Times(1)
		jobRW.EXPECT().Get(context.TODO(), gomock.Any(), gomock.Any()).Return(&batchv1.Job{
			Status: batchv1.JobStatus{
				Succeeded: 0,
				Failed:    1,
			},
		}, nil).Times(1)
		eva, err := GetEvaluationHandler().GetEvaluation(context.TODO(), message.GetEvaluationReq{})
		assert.NoError(t, err)
		assert.Equal(t, constants.EvaluationFailed, eva.Phase)
	})

	t.Run("EvaluationDone", func(t *testing.T) {
		meta.EXPECT().GetMeta(context.TODO(), gomock.Any(), gomock.Any()).Return(structs.Evaluation{}, nil).Times(1)
		jobRW.EXPECT().Get(context.TODO(), gomock.Any(), gomock.Any()).Return(&batchv1.Job{
			Status: batchv1.JobStatus{
				Succeeded: 1,
				Failed:    0,
			},
		}, nil).Times(1)
		meta.EXPECT().GetResult(context.TODO(), gomock.Any(), gomock.Any()).Return("result a", "", nil).Times(1)
		eva, err := GetEvaluationHandler().GetEvaluation(context.TODO(), message.GetEvaluationReq{})
		assert.NoError(t, err)
		assert.Equal(t, constants.EvaluationDone, eva.Phase)
		assert.Equal(t, "result a", eva.Result)
	})
}

func TestEvaluationHandler_QueryDataset(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	meta := mock_meta_manager.NewMockEvaluationMeta(ctrl)
	s3.Mock(meta)

	t.Run("no object", func(t *testing.T) {
		meta.EXPECT().GetDatasetRecords(context.TODO(), gomock.Any(), gomock.Any()).Return([]*s3api.GetObjectOutput{}, nil).Times(1)
		ds, err := GetEvaluationHandler().QueryDataset(context.TODO(), message.QueryDatasetReq{})
		assert.NoError(t, err)
		assert.Empty(t, ds)
	})

	t.Run("one object", func(t *testing.T) {
		buf := []byte("question_id,user_id,title,status,query_sql,result,result_type,feedback_type,feedback_content,created_at\nid,id,title,success,select * from table,,empty,,,\nid1,id1,title1,success,select * from table1,null,null,,,")
		tmp := CBuffer{bytes.NewBuffer(buf)}

		meta.EXPECT().GetDatasetRecords(context.TODO(), gomock.Any(), gomock.Any()).Return([]*s3api.GetObjectOutput{
			{
				Body:          &tmp,
				ContentLength: ptr.To(int64(len(buf))),
			},
		}, nil).Times(1)
		ds, err := GetEvaluationHandler().QueryDataset(context.TODO(), message.QueryDatasetReq{
			Dataset:    "ossinsight",
			Date:       "20230309",
			ResultType: []string{"null", "empty"},
		})
		assert.NoError(t, err)
		assert.NotEmpty(t, ds)
		assert.Equal(t, 2, len(ds.Records))
	})

	t.Run("multiple objects", func(t *testing.T) {
		buf := []byte("question_id,user_id,title,status,query_sql,result,result_type,feedback_type,feedback_content,created_at\nid,id,title,success,select * from table,,empty,,,")
		tmp := CBuffer{bytes.NewBuffer(buf)}
		buf1 := []byte("question_id,user_id,title,status,query_sql,result,result_type,feedback_type,feedback_content,created_at\nid1,id1,title1,success,select * from table1,null,null,,,")
		tmp1 := CBuffer{bytes.NewBuffer(buf1)}

		meta.EXPECT().GetDatasetRecords(context.TODO(), gomock.Any(), gomock.Any()).Return([]*s3api.GetObjectOutput{
			{
				Body:          &tmp,
				ContentLength: ptr.To(int64(len(buf))),
			},
			{
				Body:          &tmp1,
				ContentLength: ptr.To(int64(len(buf))),
			},
		}, nil).Times(1)
		ds, err := GetEvaluationHandler().QueryDataset(context.TODO(), message.QueryDatasetReq{
			Dataset:    "ossinsight",
			Date:       "20230309",
			ResultType: []string{"null"},
		})
		assert.NoError(t, err)
		assert.NotEmpty(t, ds)
		assert.Equal(t, 1, len(ds.Records))
	})

	t.Run("error", func(t *testing.T) {
		meta.EXPECT().GetDatasetRecords(context.TODO(), gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).Times(1)
		ds, err := GetEvaluationHandler().QueryDataset(context.TODO(), message.QueryDatasetReq{})
		assert.Error(t, err)
		assert.Empty(t, ds)
	})
}

type CBuffer struct {
	*bytes.Buffer
}

func (c *CBuffer) Close() error {
	return nil
}
