package search

import (
	"time"

	"github.com/meilisearch/meilisearch-go"
	"github.com/valyala/fasthttp"

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

const (
	SearchIndexCases          = "cases"
	SearchIndexPlans          = "plans"
	SearchIndexPlanExecutions = "plan-executions"
)

func New(meilisearchEndpoint string) service.SearchService {
	meili, err := NewMeiliSearchClient(meilisearchEndpoint)
	if err != nil {
		panic(err)
	}
	return &SearchService{Meili: meili}
}

func NewMeiliSearchClient(endpoint string) (*meilisearch.Client, error) {
	meili := meilisearch.NewFastHTTPCustomClient(
		meilisearch.ClientConfig{Host: endpoint, Timeout: 10 * time.Second},
		&fasthttp.Client{
			// Since Meilisearch idle time is 5s.
			MaxIdleConnDuration: 4 * time.Second,
			ConnPoolStrategy:    fasthttp.LIFO,
		},
	)
	_, err := meili.Health()
	if err != nil {
		return nil, err
	}
	return meili, nil
}

type SearchService struct {
	Meili *meilisearch.Client
}

func (s *SearchService) AddDocuments(ctx utils.Context, index string, documents any) (updateID int64, err error) {
	resp, err := s.Meili.Index(index).AddDocuments(documents, "id")
	if err != nil {
		ctx.Log().Errorf(err, "meilisearch: add documents failed")
		return 0, err
	}
	return resp.UID, nil
}

func (s *SearchService) SearchCases(ctx utils.Context, query string, tags []string, offset, count uint) ([]interface{}, int, error) {
	var filter = [][]string{{"deletedAt=0"}}
	for _, tag := range tags {
		filter = append(filter, []string{"tags=" + string(utils.ToJSON(tag))})
	}
	resp, err := s.Meili.Index(SearchIndexCases).Search(query, &meilisearch.SearchRequest{
		Filter:                filter,
		AttributesToHighlight: []string{"name", "summary"},
		Offset:                int64(offset),
		Limit:                 int64(count),
	})
	if err != nil {
		ctx.Log().Errorf(err, "meilisearch: search case failed")
		return nil, 0, err
	}

	return resp.Hits, int(resp.NbHits), nil
}

func (s *SearchService) SearchPlans(ctx utils.Context, query string, ty model.TestPlanType, branch string, offset, count uint) ([]interface{}, int, error) {
	// search in meilisearch
	var filter = [][]string{{"deletedAt=0"}, {"type=" + string(utils.ToJSON(ty))}}
	if branch != "" {
		filter = append(filter, []string{"branch=" + string(utils.ToJSON(branch))})
	}
	resp, err := s.Meili.Index(SearchIndexPlans).Search(query, &meilisearch.SearchRequest{
		Filter:                filter,
		AttributesToHighlight: []string{"name"},
		Offset:                int64(offset),
		Limit:                 int64(count),
	})
	if err != nil {
		ctx.Log().Errorf(err, "meilisearch: search plan failed")
		return nil, 0, err
	}

	return resp.Hits, int(resp.NbHits), nil
}

func (s *SearchService) SearchPlanExecutions(ctx utils.Context, query, email string, status *model.ExecutionStatus, triggerID model.ID, offset, count uint) ([]interface{}, int, error) {
	var filter = [][]string{}
	if email != "" {
		filter = append(filter, []string{"createdBy.email=" + string(utils.ToJSON(email))})
	}
	if status != nil {
		filter = append(filter, []string{"status=" + string(utils.ToJSON(*status))})
	}
	if triggerID != 0 {
		filter = append(filter, []string{"triggerID=" + triggerID.String()})
	}
	resp, err := s.Meili.Index(SearchIndexPlanExecutions).Search(query, &meilisearch.SearchRequest{
		Filter:                filter,
		AttributesToHighlight: []string{"plan"},
		Sort:                  []string{"createdAt:desc"},
		Offset:                int64(offset),
		Limit:                 int64(count),
	})
	if err != nil {
		ctx.Log().Errorf(err, "meilisearch: search plan-executions failed")
		return nil, 0, err
	}

	return resp.Hits, int(resp.NbHits), nil
}
