package tidblog

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/elastic/go-elasticsearch/v6"
	"github.com/pingcap/log"
	query2 "github.com/pingcap/tep/pkg/utils/es/query"
	"github.com/wushilin/stream"
	"go.uber.org/zap"
)

type tidbQuery struct {
	from      int
	size      int
	level     string
	startTime string
	endTime   string
	namespace string
	component string
}

type LogLevel string
type query func(q *tidbQuery)

var logLevel map[LogLevel]struct{}

const (
	TIDB     string   = "tidb"
	TIKV     string   = "tikv"
	PD       string   = "pd"
	NONE     LogLevel = ""
	TRACE    LogLevel = "TRACE"
	DEBUG    LogLevel = "DEBUG"
	INFO     LogLevel = "INFO"
	WARN     LogLevel = "WARN"
	ERROR    LogLevel = "ERROR"
	CRITICAL LogLevel = "CRITICAL"
	FATAL    LogLevel = "FATAL"
)

func init() {
	logLevel = map[LogLevel]struct{}{
		TRACE:    {},
		DEBUG:    {},
		INFO:     {},
		WARN:     {},
		ERROR:    {},
		CRITICAL: {},
		FATAL:    {},
	}
}

func NewTiDBQuery(q ...query) *tidbQuery {
	tidbQ := &tidbQuery{}
	stream.FromArray(q).Each(func(f interface{}) {
		f.(query)(tidbQ)
	})

	return tidbQ
}

func From(from int) query {
	if from < 0 {
		from = 0
	}

	return func(q *tidbQuery) {
		q.from = from
	}
}

func Size(size int) query {
	if size <= 0 {
		size = 10
	}

	return func(q *tidbQuery) {
		q.size = size
	}
}

func StartTimeInSec(startTimeInSec int64) query {
	if startTimeInSec <= 0 {
		startTimeInSec = time.Now().Unix() - 15*60
	}

	return func(q *tidbQuery) {
		q.startTime = query2.ConvertTimeStamp(startTimeInSec)
	}
}

func EndTimeInSec(endTimeInSec int64) query {
	if endTimeInSec <= 0 {
		endTimeInSec = time.Now().Unix()
	}

	return func(q *tidbQuery) {
		q.endTime = query2.ConvertTimeStamp(endTimeInSec)
	}
}

func Level(level LogLevel) query {
	_, ok := logLevel[level]
	if !ok {
		level = NONE
	}

	return func(q *tidbQuery) {
		q.level = string(level)
	}
}

func NameSpace(namespace string) query {
	return func(q *tidbQuery) {
		q.namespace = namespace
	}
}

func Component(component string) query {
	return func(q *tidbQuery) {
		q.component = component
	}
}

func (q *tidbQuery) Do(client *elasticsearch.Client, indexPrefix string) (*query2.QueryResponse, error) {
	builder := query2.NewQueryBuilder().
		AddEqual("kubernetes.namespace_name", q.namespace).
		AddRange("@timestamp", q.startTime, q.endTime).
		Sort("@timestamp", true).
		From(q.from).
		Size(q.size)

	if q.component == "" {
		builder.AddIn("kubernetes.labels.app_kubernetes_io/component", []interface{}{TIDB, TIKV, PD})
	} else {
		builder.AddEqual("kubernetes.labels.app_kubernetes_io/component", strings.ToLower(q.component))
	}

	if q.level != "" {
		builder.AddEqual("level", q.level)
	}

	qb := builder.BuildQuery()

	////currently, the index name is not made
	res, err := client.Search(
		client.Search.WithContext(context.Background()),
		client.Search.WithIndex(fmt.Sprintf("%s*", indexPrefix)),
		client.Search.WithBody(strings.NewReader(qb)),
		client.Search.WithPretty(),
	)

	if err != nil {
		log.Error("Query from elasticsearch failed", zap.Error(err))
		return nil, err
	}
	defer res.Body.Close()

	return query2.NewResponse(res.Body), nil
}
