package handler

import (
	"fmt"
	"net/http"
	"strings"

	"github.com/labstack/echo/v4"
	"golang.org/x/sync/errgroup"

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

type AffectedVersionCaseStatus struct {
	Version string                `json:"version"`
	Status  model.ExecutionStatus `json:"status"`
}

type AffectedVersionCase struct {
	Name     string                      `json:"name"`
	Versions []AffectedVersionCaseStatus `json:"versions"`
}

type GetAffectedVersionCasesResponse struct {
	// Trigger is the metadata for the trigger.
	Trigger *model.TestTrigger     `json:"trigger"`
	Cases   []*AffectedVersionCase `json:"cases"`
}

// GetAffectedVersionCases ...
// @Summary Get Affected Version Summary.
// @ID get-affected-version-cases
// @Tags trigger
// @Param id path integer true "Trigger ID"
// @Success 200 {object} GetAffectedVersionCasesResponse
// @Failure default {object} HTTPError
// @Router /triggers/{id}/affected-version/cases [get]
func (h *HTTPHandler) GetAffectedVersionCases(c echo.Context) error {
	ctx := c.(utils.Context)

	triggerID, err := utils.ParseID(c.Param("id"))
	if err != nil {
		return serror.New(http.StatusBadRequest, err)
	}
	log := ctx.Log().With("triggerID", triggerID)
	log.Infof("get affected version summary of trigger")

	// 1. Get Trigger.
	var trigger *model.TestTrigger
	g := errgroup.Group{}
	g.Go(func() (err error) {
		trigger, err = h.service.Trigger.Get(ctx, triggerID)
		if err != nil {
			log.Errorf(err, "get trigger failed")
			return err
		} else if trigger == nil {
			return serror.NewMsg(http.StatusNotFound, "trigger #%d not found", triggerID)
		}
		return nil
	})

	// 2. Get PlanExecutions of the Trigger.
	planExecs, _, err := h.service.PlanExec.List(ctx, common.PlanExecQueryOpts{
		TriggerIDs: []model.ID{triggerID},
		Count:      999,
	})
	if err != nil {
		log.Errorf(err, "get plan-executions failed")
		return err
	}
	if err := g.Wait(); err != nil {
		return err
	}
	if err := isAffectedVersionTrigger(trigger, planExecs); err != nil {
		return err
	}

	case2version := map[string]*AffectedVersionCase{}
	for _, exec := range planExecs {
		// 3. Get CaseExecutions of the PlanExecution.
		caseExecs, _, err := h.service.CaseExec.List(ctx, common.CaseExecQueryOpts{
			RootPlanExecID: &exec.ID,
			Count:          model.MaxPlanStepsLength,
		})
		if err != nil {
			log.Errorf(err, "get case-executions failed")
			return err
		}

		// 4. Group version status by case.
		version, _ := getAffectedVersionFromPlanName(exec.PlanIDAndName.Name)
		for _, exec := range caseExecs {
			caseName := exec.CaseIDAndName.Name
			if _, ok := case2version[caseName]; !ok {
				case2version[caseName] = &AffectedVersionCase{
					Name:     caseName,
					Versions: []AffectedVersionCaseStatus{},
				}
			}
			case2version[caseName].Versions = append(case2version[caseName].Versions, AffectedVersionCaseStatus{
				Version: version,
				Status:  exec.Status,
			})
		}
	}

	cases := make([]*AffectedVersionCase, 0, len(case2version))
	for _, v := range case2version {
		cases = append(cases, v)
	}
	return ctx.JSON(http.StatusOK, GetAffectedVersionCasesResponse{
		Trigger: trigger,
		Cases:   cases,
	})
}

// TODO(Thearas): Add some strict validation.
func isAffectedVersionTrigger(trigger *model.TestTrigger, planExecs []*model.TestPlanExecution) error {
	for _, exec := range planExecs {
		if _, err := getAffectedVersionFromPlanName(exec.PlanIDAndName.Name); err != nil {
			return serror.NewMsg(http.StatusBadRequest, "%s is not a valid affect version trigger: %v", trigger.Name, err)
		}
	}
	return nil
}

func getAffectedVersionFromPlanName(name string) (string, error) {
	xs := strings.Split(name, "@")
	if len(xs) < 2 {
		return "", fmt.Errorf("name '%s' is not in format of plan-name@version", name)
	}
	return xs[len(xs)-1], nil
}
