package handler

import (
	"fmt"
	"testing"

	"github.com/jmoiron/sqlx"
	"github.com/stretchr/testify/assert"

	"github.com/pingcap/test-infra/common/model"
	reqM "github.com/pingcap/test-infra/common/model/tcms/request"
	respM "github.com/pingcap/test-infra/common/model/tcms/response"
	htest "github.com/pingcap/test-infra/services/tcms/tests/httptest"
)

func updatePlanExecution() reqM.PlanExecutionUpdateRequest {
	return reqM.PlanExecutionUpdateRequest{
		Meta:           map[string]string{"k1": "v1", "k2": "v2"},
		StartedAt:      &[]model.Timestamp{1621997433}[0],
		CompletedAt:    &[]model.Timestamp{1621999209}[0],
		Status:         model.ExecutionStatusCancelled,
		Output:         "asdad",
		ResourcepoolID: 1,
	}
}

func TestListPlanExecutions(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	_, execs := htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions")
	assert.NotZero(t, execs.Total)

	// by plan_ids
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?plan_ids=2&status=SUCCESS&offset=0&count=2")
	assert.Equal(t, 4, execs.Total)
	assert.NotEmpty(t, execs.Data[0].Name)

	// by ids
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?ids=2,3&offset=0&count=2")
	assert.Equal(t, 2, execs.Total)

	// by trigger_ids
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?trigger_ids=2")
	assert.Equal(t, 7, execs.Total)

	// by has_living_testbed
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?has_living_testbed=1")
	assert.Equal(t, 2, execs.Total)

	// by resource_pool_id
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?resource_pool_id=1")
	assert.Equal(t, 3, execs.Total)
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?resource_pool_id=2")
	assert.Equal(t, 1, execs.Total)

	// by triage_phase
	_, execs = htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?triage_phase=TO_TRIAGE")
	assert.Equal(t, 3, execs.Total)
}

func TestGetPlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	_, exec := htest.MustGET[model.TestPlanExecution](h.Echo, "/api/v1/plan-executions/2")
	assert.Equal(t, 2, int(exec.ID))
	assert.NotNil(t, exec.StartedAt)
	assert.Equal(t, 2, len(exec.Meta))
	assert.Equal(t, 2, int(exec.PlanIDAndName.ID))
	assert.NotEmpty(t, exec.PlanIDAndName.Name)
}

func TestUpdatePlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	// update exec 1
	updateExec := updatePlanExecution()
	_, exec := htest.MustPUT[model.TestPlanExecution](h.Echo, "/api/v1/plan-executions/1", htest.WithJSON(updateExec))
	assert.Equal(t, model.ExecutionStatusCancelled, exec.Status)
	assert.Equal(t, 2, len(exec.Meta))
	assert.Equal(t, updateExec.Output, exec.Output)
	assert.Equal(t, updateExec.ResourcepoolID, exec.ResourcepoolID)
}

func TestGetPlanByPlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	_, p := htest.MustGET[model.TestPlan](h.Echo, "/api/v1/plan-executions/2/plan")
	assert.Equal(t, 2, int(p.ID))
	assert.Equal(t, "plan@v5.1.6", p.Name)
}

func TestUpdatePlanExecutionNote(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	note := "123"

	// update plan-exec 1
	_, _ = htest.MustPUT[any](h.Echo, "/api/v1/plan-executions/1/note", htest.WithJSON(reqM.ExecutionNoteUpdateRequest{Note: note}))

	// get plan-exec 1
	_, exec := htest.MustGET[model.TestPlanExecution](h.Echo, "/api/v1/plan-executions/1")
	assert.Equal(t, note, exec.Note)
}

func TestUpdatePlanExecutionReason(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	reason := model.ExecFailReasonProduct

	// update plan-exec 1
	_, _ = htest.MustPUT[any](h.Echo, "/api/v1/plan-executions/1/reason", htest.WithJSON(reqM.ExecutionReasonUpdateRequest{Reason: reason}))

	// get plan-exec 1
	_, exec := htest.MustGET[model.TestPlanExecution](h.Echo, "/api/v1/plan-executions/1")
	assert.Equal(t, reason, exec.Reason)
}

func TestRerunPlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	_, resp := htest.MustPOST[respM.PlanExecutionRerunResponse](h.Echo, "/api/v1/plan-executions/2/rerun", htest.WithUser(3))
	assert.NotZero(t, resp.ID)
	assert.NotEmpty(t, resp.URL)

	_, exec := htest.MustGET[model.TestPlanExecution](h.Echo, fmt.Sprintf("/api/v1/plan-executions/%d", resp.ID))
	assert.Equal(t, "test-version", exec.VariableBinds[0].Name)
	assert.Equal(t, "v5.1.0-test", exec.VariableBinds[0].Value)
}

func TestPartiallyRerunPlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	rerunExecID := "2"
	_, resp := htest.MustPOST[respM.PlanExecutionRerunResponse](h.Echo, fmt.Sprintf("/api/v1/plan-executions/%s/rerun?only_failed=1", rerunExecID))
	assert.NotZero(t, resp.ID)
	assert.NotEmpty(t, resp.URL)

	// check the plan-exec, plan and plan_step is exists.
	var newExecRerunSteps model.StringSlice
	assert.NoError(t, sqlx.MustConnect("mysql", h.config.DSN).Get(&newExecRerunSteps, "SELECT exec.steps FROM plan_executions exec, plans p, plan_steps s WHERE exec.plan_id=p.id AND s.plan_id=p.id AND exec.rerun_id = "+rerunExecID+" LIMIT 1"))
	assert.NotEmpty(t, newExecRerunSteps)
}

func TestCancelPlanExecution(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	// update exec 1
	_, _ = htest.MustPUT[any](h.Echo, "/api/v1/plan-executions/cancel?ids=1")

	// get exec 1
	_, exec := htest.MustGET[model.TestPlanExecution](h.Echo, "/api/v1/plan-executions/1")
	assert.Equal(t, model.ExecutionStatusCancelling, exec.Status)
}

func TestListPlanExecutionTestbeds(t *testing.T) {
	h := setup(t)
	defer h.Shutdown()

	_, resp := htest.MustGET[respM.TestBedInfoListResponse](h.Echo, "/api/v1/plan-executions/8/testbeds?count=3")
	assert.Equal(t, 4, resp.Total)
	assert.Equal(t, 3, len(resp.Data))
}
