package engine

import (
	"errors"
	"fmt"
	"strings"

	"github.com/bilibili/gengine/builder"
	"github.com/bilibili/gengine/context"
	"github.com/bilibili/gengine/engine"
	"github.com/pingcap-inc/checker/pkg/util"
	"github.com/pingcap-inc/checker/proto"
	"github.com/sirupsen/logrus"
)

type Rule struct {
	Component string
	Node      string
	Port      int
}

func (r *Rule) Mapping(sd proto.Config, rule []*proto.Rule, deviceData proto.DeviceData, rr map[string]proto.RuleResult) error {
	dataContext := context.NewDataContext()
	if sd.CheckNil() {
		r.FormatNoDataResult(rule, sd, rr)
	}
	dataContext.Add("config", sd)
	dataContext.Add("DeviceData", deviceData)
	dataContext.Add("ToInt", util.ValueToInt)
	dataContext.Add("ToString", util.ValueToString)
	dataContext.Add("ToBool", util.ValueToBool)
	dataContext.Add("ToFloat", util.ValueToFloat)
	dataContext.Add("FlatMap", util.FlatMap)
	dataContext.Add("ElemInRange", util.ElemInRange)
	ruleBuilder := builder.NewRuleBuilder(dataContext)
	rulestr := r.FormatString(rule)
	if err := ruleBuilder.BuildRuleFromString(rulestr); err != nil {
		logrus.Errorf("build rule %+v err:%s ", rule, err)
		return err
	}
	eng := engine.NewGengine()
	if err := eng.ExecuteConcurrent(ruleBuilder); err != nil {
		logrus.Errorf("execute rule error: %v", err)
		return err
	}
	result, _ := eng.GetRulesResultMap()
	err := r.FormatResultV2(result, rule, sd, rr)
	if err != nil {
		return err
	}
	return nil
}

func (r *Rule) FormatString(rules []*proto.Rule) string {
	rulestr := ""
	for _, rule := range rules {
		rulestr += rule.ExecuteRule + "\n"
	}
	return rulestr
}

func (r *Rule) FormatNoDataResult(rules []*proto.Rule, config proto.Config, rr map[string]proto.RuleResult) error {
	for _, rule := range rules {
		res := proto.DeployResult{
			Id:    fmt.Sprintf("%s_%s:%d", config.GetComponent(), config.GetHost(), config.GetPort()),
			Value: r.GetValue(rule.Variation, config),
			Res:   "NoData",
		}
		ds, ok := rr[rule.Name]
		if ok {
			ds.DeployResults = append(ds.DeployResults, res)
		} else {
			ds := proto.RuleResult{
				RuleName:     rule.Name,
				RuleId:       rule.Id,
				Variation:    rule.Variation,
				AlertingRule: rule.AlertingRule,
				Suggestion:   rule.Suggestion,
			}
			ds.DeployResults = []proto.DeployResult{res}
			rr[rule.Name] = ds
		}
	}
	return nil
}

func (r *Rule) FormatResultV2(rawresult map[string]interface{}, rules []*proto.Rule, config proto.Config, rr map[string]proto.RuleResult) error {
	for _, rule := range rules {
		val, ok := rawresult[rule.Name]
		if !ok {
			logrus.Error("no rule result mapping,", rule.Name)
			return errors.New("no rule result mapping")
		}
		mappingRes, _ := val.(bool)
		var Pass string
		if mappingRes {
			Pass = "OK"
		} else {
			Pass = rule.WarnLevel
		}
		res := proto.DeployResult{
			Id:    fmt.Sprintf("%s_%s:%d", config.GetComponent(), config.GetHost(), config.GetPort()),
			Value: r.GetValue(rule.Variation, config),
			Res:   Pass,
		}
		ds, ok := rr[rule.Name]
		if ok {
			ds.DeployResults = append(ds.DeployResults, res)
		} else {
			ds := proto.RuleResult{
				RuleName:     rule.Name,
				RuleId:       rule.Id,
				Variation:    rule.Variation,
				AlertingRule: rule.AlertingRule,
				Suggestion:   rule.Suggestion,
			}
			ds.DeployResults = []proto.DeployResult{res}
			rr[rule.Name] = ds
		}
	}
	return nil
}

func (r *Rule) GetValue(valpath string, config proto.Config) string {
	valpaths := strings.Split(valpath, ",")
	valmap := []string{}
	for _, valpath := range valpaths {
		if len(valpath) != 0 {
			rv := config.GetValueByTagPath(valpath) // empty will coredump
			valmap = append(valmap, fmt.Sprintf("%s.%s:%v", config.GetComponent(), valpath, rv))
		}
	}
	return strings.Join(valmap, ",")
}
