package schedule

import (
	"context"
	"encoding/json"
	"io/ioutil"
	"testing"

	"github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1"
	"github.com/golang/mock/gomock"
	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/services/tps/mocks"
	"github.com/pingcap/test-infra/services/tps/pkg/common"
	"github.com/stretchr/testify/require"
)

var (
	gPlanExec  = &model.TestPlanExecution{}
	gPlan      = &model.TestPlan{}
	gWorkFlow  = &v1alpha1.Workflow{}
	gCaseExec1 = &model.TestCaseExecution{}
	gCaseExec2 = &model.TestCaseExecution{}
	gCaseExec3 = &model.TestCaseExecution{}
)

func setup(t *testing.T) {
	testPlanStr, err := ioutil.ReadFile("fixtures/testplan.json")
	require.NoError(t, err)
	err = json.Unmarshal(testPlanStr, gPlanExec)
	require.NoError(t, err)

	planStr, err := ioutil.ReadFile("fixtures/plan-540832.json")
	require.NoError(t, err)
	err = json.Unmarshal(planStr, gPlan)
	require.NoError(t, err)

	workFlowStr, err := ioutil.ReadFile("fixtures/workflow.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, gWorkFlow)
	require.NoError(t, err)

	caseExecStr, err := ioutil.ReadFile("fixtures/testcase-1131485.json")
	require.NoError(t, err)
	err = json.Unmarshal(caseExecStr, gCaseExec1)
	require.NoError(t, err)

	caseExecStr, err = ioutil.ReadFile("fixtures/testcase-1131486.json")
	require.NoError(t, err)
	err = json.Unmarshal(caseExecStr, gCaseExec2)
	require.NoError(t, err)

	caseExecStr, err = ioutil.ReadFile("fixtures/testcase-1131487.json")
	require.NoError(t, err)
	err = json.Unmarshal(caseExecStr, gCaseExec3)
	require.NoError(t, err)
}

type argoClientMgrStub struct {
	client ArgoClient
}

func (a argoClientMgrStub) ClientFor(job *common.ArgoJob) (ArgoClient, error) {
	return a.client, nil
}
func (argoClientMgrStub) BindClient(job *common.ArgoJob, client ArgoClient) *common.ArgoJob {
	return job
}

type argoClientGetStub struct {
	expect func(act string)
	ret    *v1alpha1.Workflow
}

func (a argoClientGetStub) Create(ctx context.Context, workflow *v1alpha1.Workflow) (*v1alpha1.Workflow, error) {
	panic("not implemented")
}

func (a argoClientGetStub) Get(ctx context.Context, name string) (*v1alpha1.Workflow, error) {
	a.expect(name)
	return a.ret, nil
}

func (a argoClientGetStub) Stop(ctx context.Context, name string) (*v1alpha1.Workflow, error) {
	panic("not implemented")
}

func (a argoClientGetStub) URL() string {
	return idcArgoURL
}

func argoMgrForGet(t *testing.T, expect string, ret *v1alpha1.Workflow) ArgoClientMgr {
	return argoClientMgrStub{client: argoClientGetStub{expect: func(act string) {
		require.Equal(t, expect, act)
	},
		ret: ret,
	}}
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to ERROR
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-0-0-failed.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())
	mTCMS.EXPECT().SyncTestBed(gomock.Any(), gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusError, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to RUNNING
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-2-2-setup-failed.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusPrepareBackoff, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to RUNNING
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-5-1-running.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any())
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusRunning, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to RUNNING
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-5-5-failed.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131486))).Return(gCaseExec2, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131487))).Return(gCaseExec3, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any()).Times(3)
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())
	mTCMS.EXPECT().SyncTestBed(gomock.Any(), gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusFailure, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to Succeeded
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-5-5-succeeded.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131486))).Return(gCaseExec2, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131487))).Return(gCaseExec3, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any()).Times(3)
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())
	mTCMS.EXPECT().SyncTestBed(gomock.Any(), gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusSuccess, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to RUNNING
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-5-1-running.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any())
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusRunning, gPlanExec.Status)

	// RUNNING to FAILURE
	workflow2 := &v1alpha1.Workflow{}
	workFlowStr, err = ioutil.ReadFile("fixtures/workflow-5-5-failed.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow2)
	require.NoError(t, err)

	ss.Argo = argoMgrForGet(t, "plan-exec-780895", workflow2)

	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131486))).Return(gCaseExec2, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131487))).Return(gCaseExec3, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any()).Times(3)
	mTCMS.EXPECT().SyncTestBed(gomock.Any(), gomock.Any())

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusFailure, gPlanExec.Status)
}

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

	job := &common.ArgoJob{
		PlanExec: gPlanExec,
		Plan:     gPlan,
		Workflow: gWorkFlow,
		Status:   common.ArgoTestJobPending,
	}

	setup(t)

	// PREPARING to RUNNING
	workflow := &v1alpha1.Workflow{}
	workFlowStr, err := ioutil.ReadFile("fixtures/workflow-5-1-running.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow)
	require.NoError(t, err)

	ctx := context.Background()

	mTCMS := mocks.NewMockTCMS(ctrl)
	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any())
	mTCMS.EXPECT().PutTestBedInfo(gomock.Any())

	ss := StatusSyncer{
		TCMS: mTCMS,
		Argo: argoMgrForGet(t, "plan-exec-780895", workflow),
	}

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusRunning, gPlanExec.Status)

	// RUNNING to FAILURE
	workflow2 := &v1alpha1.Workflow{}
	workFlowStr, err = ioutil.ReadFile("fixtures/workflow-5-5-succeeded.json")
	require.NoError(t, err)
	err = json.Unmarshal(workFlowStr, workflow2)
	require.NoError(t, err)

	ss.Argo = argoMgrForGet(t, "plan-exec-780895", workflow2)

	mTCMS.EXPECT().PutPlanExecution(gPlanExec)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131485))).Return(gCaseExec1, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131486))).Return(gCaseExec2, nil)
	mTCMS.EXPECT().GetCaseExecution(gomock.Eq(model.ID(1131487))).Return(gCaseExec3, nil)
	mTCMS.EXPECT().PutCaseExecution(gomock.Any()).Times(3)
	mTCMS.EXPECT().SyncTestBed(gomock.Any(), gomock.Any())

	require.NoError(t, ss.Process(ctx, job))
	require.Equal(t, model.ExecutionStatusSuccess, gPlanExec.Status)
}
