package handler

import (
	"encoding/json"
	"net/http"
	"testing"

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

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/common/model/resource"
	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 newOnceTrigger() reqM.TriggerCreateRequest {
	return reqM.TriggerCreateRequest{
		Name:    "trig1",
		Summary: "an once trigger",
		Type:    model.TriggerTypeOnce,
		Expr:    "2021-09-03T00:00:00Z",
		Active:  false,
		PlanRef: model.PlanRef{
			PlanID: 1,
			VariableBinds: []model.PlanVarBind{{
				Name:  "var1",
				Value: "value1",
			}},
		},
	}
}

func newReleaseTrigger() reqM.TriggerCreateRequest {
	return reqM.TriggerCreateRequest{
		Name:                    "release-v100.0",
		Summary:                 "release v100.0",
		ReleaseBranch:           "release-5.0",
		AutoIncludeReleasePlans: true,
		Type:                    model.TriggerTypeRelease,
		Active:                  true,
	}
}

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

	_, triggers := htest.MustGET[respM.TriggerListResponse](h.Echo, "/api/v1/triggers?plan_id=2&creator=2&offset=0&count=2")
	assert.Equal(t, 2, triggers.Total)
	assert.Equal(t, 2, int(triggers.Data[0].PlanID))
}

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

	_, triggers := htest.MustGET[respM.TriggerListResponse](h.Echo, "/api/v1/triggers/autocomplete?query=t")
	assert.Equal(t, 2, triggers.Total)
}

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

	_, tp := htest.MustGET[model.TestTrigger](h.Echo, "/api/v1/triggers/1")
	assert.Equal(t, 1, int(tp.ID))
	assert.Equal(t, 1, int(tp.CreatedBy.ID))
	assert.False(t, tp.CreatedAt.IsZero())
}

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

	once := newOnceTrigger()
	creatorID := model.ID(3)
	_, tt := htest.MustPOST[model.TestTrigger](h.Echo, "/api/v1/triggers", htest.WithJSON(once), htest.WithUser(creatorID))
	assert.Equal(t, once.PlanID, tt.PlanID)
	assert.Empty(t, tt.PlanIDs)
	assert.Equal(t, once.Name, tt.Name)
	assert.Equal(t, creatorID, tt.CreatedBy.ID)

	// with inline plans
	inlinePlanName := "myplan-on-v5.4.0"
	once.PlanID = 0
	once.InlinePlans = []*reqM.TriggerCreateRequestInlinePlan{{
		TestPlanBase: model.TestPlanBase{
			Name: inlinePlanName,
			TestBed: model.TestBed{
				GenerateName: "123123",
				Items: []model.TestBedItem{{
					ResourceHandle: resource.ResourceHandle{
						Name: "tc",
						Type: "TIDB_CLUSTER",
					},
					Spec: json.RawMessage(`{
					"tidb": {
					  "replicas": 1,
					  "image": "hub.pingcap.net/qa/tidb:master"
					},
					"tikv": {
					  "repliacs": 2,
					  "image": "hub.pingcap.net/qa/tikv:master",
					  "config": "[storage]\nreserve-space = 0\n"
					},
					"pd": {
					  "repliacs": 3,
					  "image": "hub.pingcap.net/qa/tipd:master"
					}
				  }`),
				}},
			},
			Pipeline: []model.PipelineStep{{
				Name: "bank-pessimistic-inline-case",
				// ref to inline case
				CaseRef: model.CaseRef{
					CaseName: "inline-case-1",
					Arguments: []model.CaseParamBind{{
						Name:  "tc",
						Value: "{{testbed.tc}}",
					}},
				},
			}, {
				Name: "bank-pessimistic-inline-case2",
				// ref to inline case
				CaseRef: model.CaseRef{
					CaseName: "inline-case-2",
					Arguments: []model.CaseParamBind{{
						Name:  "tc",
						Value: "{{testbed.tc}}",
					}},
				},
			}},
			FailFast: false,
		},
		PlanCreateRequestOneshotSpec: reqM.PlanCreateRequestOneshotSpec{
			InlineCases: []*model.TestCaseBase{{
				Name: "inline-case-1",
				Framework: model.FrameworkUnion{
					Generic: &model.GenericFrameworkSpec{
						Command: []string{"ls", "-l"},
						Image:   "debian",
					},
				},
				Parameters: []model.CaseParamDecl{
					{Type: model.CaseParamTypeResource, Name: "tc"},
				},
			}, {
				Name: "inline-case-2",
				Framework: model.FrameworkUnion{
					Generic: &model.GenericFrameworkSpec{
						Command: []string{"ls", "-l"},
						Image:   "debian",
					},
				},
				Parameters: []model.CaseParamDecl{
					{Type: model.CaseParamTypeResource, Name: "tc"},
				},
			}},
		},
	}}
	_, tt = htest.MustPOST[model.TestTrigger](h.Echo, "/api/v1/triggers", htest.WithJSON(once))
	assert.Zero(t, tt.PlanID)

	// check inline plan
	var inlinePlanID model.ID
	err := sqlx.MustConnect("mysql", h.config.DSN).Get(&inlinePlanID, "SELECT id FROM plans WHERE deleted_at != 0 AND name = '"+inlinePlanName+"'")
	assert.NoError(t, err)
	assert.Equal(t, inlinePlanID, tt.PlanIDs[0])
}

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

	rlsTrigger := newReleaseTrigger()

	_, tt := htest.MustPOST[model.TestTrigger](h.Echo, "/api/v1/triggers", htest.WithJSON(rlsTrigger))
	assert.Equal(t, model.IDSlice{4}, tt.PlanIDs)
	assert.Equal(t, rlsTrigger.Name, tt.Name)
}

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

	addPlanIDsReq := reqM.UpdateTriggerPlanRefRequest{
		NewPlanIDs: []model.ID{5},
	}

	// update trigger 3's planIDs (add plan 5)
	_, _ = htest.MustPUT[any](h.Echo, "/api/v1/triggers/3/plan-ref", htest.WithJSON(addPlanIDsReq))

	// assert plan 5 is in planIDs of trigger 3
	_, tp := htest.MustGET[model.TestTrigger](h.Echo, "/api/v1/triggers/3")
	assert.Equal(t, 3, int(tp.ID))
	assert.Equal(t, 1, int(tp.CreatedBy.ID))
	assert.Equal(t, model.IDSlice{4, 5}, tp.PlanIDs)
	assert.False(t, tp.CreatedAt.IsZero())

	// assert corresponding plan-executions have been created
	_, execs := htest.MustGET[respM.PlanExecutionListResponse](h.Echo, "/api/v1/plan-executions?trigger_ids=3")
	assert.Equal(t, 6, execs.Total)
	if assert.Equal(t, 6, len(execs.Data)) {
		assert.Equal(t, 3, int(execs.Data[0].TriggerID))
		assert.Equal(t, 3, int(execs.Data[1].TriggerID))
		assert.Equal(t, 5, int(execs.Data[0].PlanIDAndName.ID))
		assert.Equal(t, 2, int(execs.Data[1].PlanIDAndName.ID))
	}
}

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

	// delete trigger 1
	_, result := htest.MustDELETE[map[string]string](h.Echo, "/api/v1/triggers/1", htest.WithUser(1))
	assert.Equal(t, "ok", result["result"])

	// get trigger 1
	recErr, _ := htest.GET[any](h.Echo, "/api/v1/triggers/1")
	assert.Equal(t, http.StatusNotFound, recErr.Code)
}
