package handler

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

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"

	reqM "github.com/pingcap/test-infra/common/model/tcms/request"

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

var updateFailurePlanExecution = reqM.PlanExecutionUpdateRequest{
	Status: model.ExecutionStatusFailure,
}

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

	upExec := utils.ToJSON(updateFailurePlanExecution)

	// update exec 1
	req := httptest.NewRequest(echo.PUT, "/api/v1/plan-executions/:id", bytes.NewReader(upExec))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/plan-executions/:id")
	ctx.SetParamNames("id")
	ctx.SetParamValues("6")
	assert.NoError(t, h.UpdatePlanExecution(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		exec := model.TestPlanExecution{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &exec))
		assert.Equal(t, model.ExecutionStatusFailure, exec.Status)
	}

	// get triage by execID
	triage := model.Triage{}
	req = httptest.NewRequest(echo.GET, "/api/v1/plan-executions/:id/triage", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/plan-executions/:id/triage")
	ctx.SetParamNames("id")
	ctx.SetParamValues("6")
	assert.NoError(t, WithAuth(ctx, h.GetTriage, 1))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &triage))
		assert.Equal(t, model.TriagePhaseToTriage, triage.TriagePhase)
		assert.Equal(t, 4, len(triage.ToTriageCaseExecIDs))
		assert.Equal(t, model.ID(8), triage.ToTriageCaseExecIDs[0])
		assert.Equal(t, model.ID(10), triage.ToTriageCaseExecIDs[1])
		assert.Equal(t, model.ID(12), triage.ToTriageCaseExecIDs[2])
		assert.Equal(t, model.ID(13), triage.ToTriageCaseExecIDs[3])
	}

	// prepare for update
	triage.TriagePhase = model.TriagePhaseTriaging
	updateTriage := utils.ToJSON(reqM.PlanTriageUpdateRequest{
		TriagePhase: &triage.TriagePhase,
	})

	// update triage
	req = httptest.NewRequest(echo.PUT, "/api/v1/plan-executions/:id/triage", bytes.NewReader(updateTriage))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/plan-executions/:id/triage")
	ctx.SetParamNames("id")
	ctx.SetParamValues("6")
	assert.NoError(t, WithAuth(ctx, h.UpdateTriage, 0))
	assert.Equal(t, http.StatusOK, rec.Code)
}

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

	// prepare for update
	triagePhase := model.TriagePhaseTriaging
	updateTriage := utils.ToJSON(reqM.PlanTriageUpdateRequest{
		TriagePhase: &triagePhase,
	})

	// update triage 2
	req := httptest.NewRequest(echo.PUT, "/api/v1/plan-executions/:id/triage", bytes.NewReader(updateTriage))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec := httptest.NewRecorder()
	ctx := utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/plan-executions/:id/triage")
	ctx.SetParamNames("id")
	ctx.SetParamValues("2")
	assert.NoError(t, WithAuth(ctx, h.UpdateTriage, 0))
	assert.Equal(t, http.StatusOK, rec.Code)

	// create caseTriage for caseExec 6
	caseTriage := &model.CaseTriage{
		PlanExecutionID: model.ID(2),
		CaseExecutionID: model.ID(6),
		Reason:          model.CaseExecFailReasonEnv,
		Solution:        model.ExecFailSolutionPAUSE,
		IssueLink:       "link",
		Note:            "note",
	}
	req = httptest.NewRequest(echo.POST, "/api/v1/case-executions/triages", bytes.NewReader(utils.ToJSON(caseTriage)))
	req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/case-executions/triages")
	assert.NoError(t, h.CreateCaseTriage(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		triage := model.CaseTriage{}
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &triage))
		assert.Equal(t, model.CaseExecFailReasonEnv, triage.Reason)
		assert.Equal(t, model.ExecFailSolutionPAUSE, triage.Solution)
		assert.Equal(t, "link", triage.IssueLink)
		assert.Equal(t, "note", triage.Note)
		assert.Equal(t, model.ID(0), triage.ForkedFrom)
	}

	// suggest related caseTriages exclude self
	req = httptest.NewRequest(echo.GET, "/api/v1/case-executions/:id/triage/suggest", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/case-executions/:id/triage/suggest")
	ctx.SetParamNames("id")
	ctx.SetParamValues("6")
	assert.NoError(t, h.SuggestCaseTriage(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var suggestions []model.CaseTriage
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &suggestions))
		assert.Equal(t, 0, len(suggestions))
	}

	// suggest related caseTriages
	req = httptest.NewRequest(echo.GET, "/api/v1/case-executions/:id/triage/suggest", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/case-executions/:id/triage/suggest")
	ctx.SetParamNames("id")
	ctx.SetParamValues("7")
	assert.NoError(t, h.SuggestCaseTriage(ctx))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var suggestions []model.CaseTriage
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &suggestions))
		assert.Equal(t, 1, len(suggestions))
		assert.Equal(t, model.ID(6), suggestions[0].CaseExecutionID)
	}

	// fork caseTriage 6 for caseExec 7
	req = httptest.NewRequest(echo.PUT, "/api/v1/case-executions/:id/triage/fork?from=6", nil)
	rec = httptest.NewRecorder()
	ctx = utils.NewContext(h.Echo.NewContext(req, rec))
	ctx.SetPath("/api/v1/case-executions/:id/triage/suggest")
	ctx.SetParamNames("id")
	ctx.SetParamValues("7")
	assert.NoError(t, WithAuth(ctx, h.ForkCaseTriage, 0))
	if assert.Equal(t, http.StatusOK, rec.Code) {
		var forked model.CaseTriage
		assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &forked))
		assert.Equal(t, model.CaseExecFailReasonEnv, forked.Reason)
		assert.Equal(t, model.ExecFailSolutionPAUSE, forked.Solution)
		assert.Equal(t, "link", forked.IssueLink)
		assert.Equal(t, "note", forked.Note)
		assert.Equal(t, model.ID(6), forked.ForkedFrom)
	}
}
