package scheduled

import (
	"time"

	"github.com/labstack/echo/v4"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/services/tcms/service"
	"github.com/pingcap/test-infra/services/tcms/service/common"
	"github.com/pingcap/test-infra/services/tcms/utils"
)

func NewTestbedInfoSyncer(jwtKey []byte, planExecSvc service.PlanExecutionService, testbedSvc service.TestbedService) *testbedInfoSyncer {
	fakeJWTToken := "Bearer " + utils.JWTClaims{
		User: utils.SessionUser{
			ID: 0,
		},
	}.Encode(jwtKey)
	return &testbedInfoSyncer{
		fakeJWTToken: fakeJWTToken,
		planExecSvc:  planExecSvc,
		testbedSvc:   testbedSvc,
	}
}

type testbedInfoSyncer struct {
	fakeJWTToken string
	planExecSvc  service.PlanExecutionService
	testbedSvc   service.TestbedService
}

func (t *testbedInfoSyncer) Run() {
	ctx := utils.FakeCtx()
	ctx.Request().Header.Set(echo.HeaderAuthorization, t.fakeJWTToken)

	initiatingTestbedInfos, _, err := t.testbedSvc.List(ctx, common.TestbedQueryOpts{
		Status: []model.TestBedInfoStatus{model.TestBedInfoStatusInitiating},
		Count:  999,
	})
	if err != nil {
		ctx.Log().Errorf(err, "testbedInfoSyncer query INITIATING testbedInfos failed")
		return
	}
	for _, testbed := range initiatingTestbedInfos {
		if testbed.PlanExecutionID == 0 {
			// ignore, not every testbed has a plan-execution
			continue
		}
		exec, err := t.planExecSvc.Get(ctx, testbed.PlanExecutionID)
		if err != nil {
			ctx.Log().With("PlanExecID", testbed.PlanExecutionID).Errorf(err, "testbedInfoSyncer query plan-executions failed")
			continue
		} else if exec == nil {
			ctx.Log().With("PlanExecID", testbed.PlanExecutionID).Warnf("testbedInfoSyncer query plan-executions not found")
			continue
		}
		if exec.Status.IsCompleted() && exec.Status != model.ExecutionStatusCancelled {
			ctx.Log().With("PlanExecID", testbed.PlanExecutionID).Warnf("unexpected testbed: %+v", testbed)

			// update status for Get
			if err = t.testbedSvc.Update(ctx, common.TestbedUpdateOpts{
				PlanExecID:    &testbed.PlanExecutionID,
				Name:          testbed.Name,
				UpdatedStatus: model.TestBedInfoStatusReady,
			}); err != nil {
				ctx.Log().With("PlanExecID", testbed.PlanExecutionID).Warnf("update testbed %s error: %s", testbed.Name, err.Error())
				continue
			}
		}
	}

	readyTestbedInfos, _, err := t.testbedSvc.List(ctx, common.TestbedQueryOpts{
		Status: []model.TestBedInfoStatus{model.TestBedInfoStatusReady},
		Count:  999,
	})
	if err != nil {
		ctx.Log().Errorf(err, "testbedInfoSyncer query READY testbedInfos failed")
		return
	}
	for _, testbed := range readyTestbedInfos {
		testbedName := testbed.Name
		if testbed.LeaseDDL == nil || time.Now().After(*testbed.LeaseDDL) {
			testbed, err = t.testbedSvc.Get(ctx, testbed.PlanExecutionID, testbedName, true)
			if err != nil {
				ctx.Log().With("testbed", testbedName).Warnf("get testbed error: %s", err.Error())
				continue
			}
			ctx.Log().With("testbed.Name", testbed.Name).Infof("synced testbed: %+v", testbed)
			continue
		}

		exec, err := t.planExecSvc.Get(ctx, testbed.PlanExecutionID)
		if err != nil {
			ctx.Log().With("PlanExecID", testbed.PlanExecutionID).Errorf(err, "testbedInfoSyncer query plan-executions failed")
			continue
		}
		if exec.Status.IsCompleted() {
			testbed, err = t.testbedSvc.Get(ctx, testbed.PlanExecutionID, testbedName, false)
			if err != nil {
				ctx.Log().With("testbed", testbedName).Warnf("get testbed error: %s", err.Error())
				continue
			}
			ctx.Log().With("testbed.Name", testbed.Name).Infof("synced testbed: %+v", testbed)
		}
	}
}
