package experiment

import (
	"context"
	"fmt"

	"github.com/tidbcloud/chaos-svc/idl/pbgen/proto/chaos"
	"github.com/tidbcloud/chaos-svc/pkg/domain/entity"
	"github.com/tidbcloud/chaos-svc/pkg/domain/repo"
	"github.com/tidbcloud/chaos-svc/pkg/domain/service/workflow"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
)

type ListExperimentSvc struct {
	workflowRepo  repo.WorkflowRepo
	specConverter workflow.SpecConverter
}

func NewListExperimentSvc(
	workflowRepo repo.WorkflowRepo,
	specConverter workflow.SpecConverter,
) *ListExperimentSvc {
	return &ListExperimentSvc{
		workflowRepo:  workflowRepo,
		specConverter: specConverter,
	}
}

func (svc *ListExperimentSvc) Execute(ctx context.Context, req *chaos.ListExperimentsRequest) ([]*chaos.Experiment, int64, bizerr.BizError) {
	if err := svc.PreProcessing(req); err != nil {
		return nil, 0, err
	}

	workflowFilter := ExperimentFilter2Repo(req.Filter)
	workflowSorters, err := ExperimentSorters2Repo(req.Sorters)
	if err != nil {
		return nil, 0, err
	}

	count, err := svc.workflowRepo.Count(ctx, workflowFilter)
	if err != nil {
		return nil, 0, err
	}
	if count == 0 {
		return nil, 0, nil
	}
	experiments, err := svc.workflowRepo.Query(ctx, workflowFilter, req.Pager, workflowSorters)
	if err != nil {
		return nil, 0, err
	}

	pbExperiments := make([]*chaos.Experiment, 0, len(experiments))
	for _, each := range experiments {
		experimentPb, err := WorkflowEntity2Pb(svc.specConverter, each)
		if err != nil {
			return nil, 0, err
		}
		pbExperiments = append(pbExperiments, experimentPb)
	}
	return pbExperiments, count, nil
}

func (svc *ListExperimentSvc) PreProcessing(req *chaos.ListExperimentsRequest) bizerr.BizError {
	if err := svc.Validate(req); err != nil {
		return err
	}
	svc.Default(req)
	return nil
}

func (svc *ListExperimentSvc) Validate(req *chaos.ListExperimentsRequest) bizerr.BizError {
	if req.Filter == nil {
		return bizerr.New(bizerr.ErrCodeGeneralBusiness, "empty filter")
	}
	if len(req.Filter.ClusterIds) == 0 {
		return bizerr.New(bizerr.ErrCodeGeneralBusiness, "empty cluster ids")
	}
	return nil
}

func (svc *ListExperimentSvc) Default(req *chaos.ListExperimentsRequest) {
	if req.Pager == nil {
		req.Pager = &base.Pager{}
	}
	if req.Pager.Page == 0 {
		req.Pager.Page = 1
	}
	if req.Pager.PageSize == 0 {
		req.Pager.PageSize = 30
	}
	if req.Sorters == nil {
		req.Sorters = []*base.Sorter{
			{
				OrderBy: "created_at",
				Sort:    base.Sort_SORT_DESC,
			},
		}
	}
}

func WorkflowEntity2Pb(specConverter workflow.SpecConverter, workflow *entity.Workflow) (*chaos.Experiment, bizerr.BizError) {
	var deletedAt *int64
	if workflow.DeletedAt != nil {
		tmp := workflow.DeletedAt.Unix()
		deletedAt = &tmp
	}
	var startedAt *int64
	if workflow.StartedAt != nil {
		tmp := workflow.StartedAt.Unix()
		startedAt = &tmp
	}
	var completedAt *int64
	if workflow.CompletedAt != nil {
		tmp := workflow.CompletedAt.Unix()
		completedAt = &tmp
	}
	action, err := specConverter.Entity2Pb(workflow.Spec)
	if err != nil {
		return nil, err
	}
	return &chaos.Experiment{
		ExperimentId: workflow.ID,
		ClusterId:    workflow.ClusterID,
		Draft:        workflow.Draft,
		Status:       workflow.Status,
		Message:      workflow.Message,
		Action:       action,
		Summary:      &workflow.Summary,
		ReportLink:   &workflow.ReportLink,
		CreatedById:  workflow.CreatedByID,
		CreatedAt:    workflow.CreatedAt.Unix(),
		StartedAt:    startedAt,
		CompletedAt:  completedAt,
		DeletedAt:    deletedAt,
	}, nil
}

func ExperimentFilter2Repo(filter *chaos.ExperimentFilter) repo.WorkflowFilter {
	return repo.WorkflowFilter{
		ClusterIDs:  filter.ClusterIds,
		WorkflowIDs: filter.ExperimentIds,
		Deleted:     filter.Deleted,
	}
}

func ExperimentSorters2Repo(sorters []*base.Sorter) ([]*repo.WorkflowSorter, bizerr.BizError) {
	workflowSorters := make([]*repo.WorkflowSorter, 0, len(sorters))
	for _, sorter := range sorters {
		sorter := sorter
		workflowSorter, err := buildWorkflowSorter(sorter)
		if err != nil {
			return nil, err
		}
		workflowSorters = append(workflowSorters, workflowSorter)
	}
	return workflowSorters, nil
}

func buildWorkflowSorter(sorter *base.Sorter) (*repo.WorkflowSorter, bizerr.BizError) {
	switch sorter.OrderBy {
	case "experiment_id":
		fallthrough
	case "id":
		return &repo.WorkflowSorter{
			OrderBy: repo.WorkflowOrderByID,
			Desc:    isSorterDesc(sorter),
		}, nil
	case "cluster_id":
		return &repo.WorkflowSorter{
			OrderBy: repo.WorkflowOrderByClusterID,
			Desc:    isSorterDesc(sorter),
		}, nil
	case "created_at":
		return &repo.WorkflowSorter{
			OrderBy: repo.WorkflowOrderByCreatedAt,
			Desc:    isSorterDesc(sorter),
		}, nil
	}
	return nil, bizerr.New(bizerr.ErrCodeGeneralBusiness, fmt.Sprintf("not support order by this field: %s", sorter.OrderBy))
}

func isSorterDesc(sorter *base.Sorter) bool {
	return sorter.Sort != base.Sort_SORT_ASC
}
