package validate

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/prometheus-community/jiralert/pkg/alertmanager"
	"github.com/prometheus-community/jiralert/pkg/client"
	"github.com/prometheus-community/jiralert/pkg/clinic"
)

func (v *ValidateHandler) validateByEvent(
	params map[string]interface{},
	data *alertmanager.Data,
	issue *client.Issue,
) (comments []string, err error) {
	// var durationInt int
	var events []string
	var clusterIDStr string
	comments = append(comments, "precheck by EVENT")
	debugComments := make([]string, 0)
	debugComments = append(debugComments, "---")
	debugComments = append(debugComments, "DEBUG INFO")
	defer func() {
		comments = append(comments, debugComments...)
	}()

	// if duration, ok := params[`durationSecond`]; ok {
	// 	durationInt, ok = duration.(int)
	// 	if !ok {
	// 		return comments, fmt.Errorf("cannot parse duration as int")
	// 	}
	// } else {
	// 	return comments, fmt.Errorf("duration is empty")
	// }
	if eventNames, ok := params[`eventNames`]; ok {
		events = strings.Split(eventNames.(string), "|")
	} else {
		return comments, fmt.Errorf("eventNames is empty")
	}
	issueStartAt := data.Alerts[0].StartsAt.Unix()
	var clusterIDRaw string
	if clusterID, ok := data.CommonLabels[`tidb_cluster_id`]; ok {
		clusterIDRaw = clusterID
	} else {
		if clusterID2, ok := data.CommonLabels[`cluster_id`]; ok {
			clusterIDRaw = clusterID2
		}
	}
	if clusterIDRaw == "" {
		return comments, fmt.Errorf("fail to extract cluster id from alert raw data")
	}
	reg, _ := regexp.Compile("[^0-9]+")
	clusterIDStr = reg.ReplaceAllString(clusterIDRaw, "")

	shortDurationFrom := int(issueStartAt) - 3600
	shortDurationTo := int(issueStartAt)
	longDurationFrom := int(issueStartAt) - 86400
	longDurationTo := int(issueStartAt) - 1800

	if filter, cm, debugCm, err := v.filterByEvents(
		shortDurationFrom,
		shortDurationTo,
		"short",
		clusterIDStr,
		events,
		filterByShortDurationEvents,
		params,
		issue); err != nil {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
		return comments, err
	} else if filter {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
		return comments, nil
	} else {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
	}
	if filter, cm, debugCm, err := v.filterByEvents(
		longDurationFrom,
		longDurationTo,
		"long",
		clusterIDStr,
		events,
		filterByLongDurationEvents,
		params,
		issue); err != nil {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
		return comments, err
	} else if filter {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
		return comments, nil

	} else {
		comments = append(comments, cm...)
		debugComments = append(debugComments, debugCm...)
		return comments, nil
	}
}

func (v *ValidateHandler) filterByEvents(from, to int,
	durationType string,
	clusterIDStr string,
	events []string,
	f func(activities []*clinic.Activity, events []string) *clinic.Activity,
	params map[string]interface{},
	issue *client.Issue,
) (result bool, comments []string, debugComments []string, err error) {
	debugComments = append(debugComments, fmt.Sprintf("%s duration event, from: %d, to: %d", durationType, from, to))
	activities, err := v.clinic.GetActivities(from, to, clusterIDStr)
	if err != nil {
		return false, comments, debugComments, err
	}

	// print all the fetched activities
	nameMap := make(map[string]interface{}, 0)
	for _, act := range activities {
		if _, ok := nameMap[act.DisplayName]; !ok {
			nameMap[act.DisplayName] = struct{}{}
		}
	}
	names := make([]string, 0)
	for k := range nameMap {
		names = append(names, k)
	}
	debugComments = append(debugComments,
		fmt.Sprintf("%s duration activity count: %d, activity names: %v",
			durationType, len(activities), names))
	fmt.Printf("all %s duration activities: %v\n", durationType, names)

	if event := f(activities, events); event != nil {
		// set priority string.
		var priorityStr string
		if priority, ok := params[`priority`]; ok {
			priorityStr, ok = priority.(string)
			if !ok {
				return false, comments, debugComments, fmt.Errorf("cannot parse priority as string")
			}
			issue.Priority = priorityStr
			comments = append(comments, fmt.Sprintf("Action: priority is set to Major because event [%s] happened recently", event.DisplayName))
		}
		// set fake alarm.
		var fakeBool bool
		if fake, ok := params[`fake`]; ok {
			fakeBool, ok = fake.(bool)
			if !ok {
				return false, comments, debugComments, fmt.Errorf("cannot parse fake as bool")
			}
			// fake alarm field
			// MIGRATE RELATED
			issue.CustomFields[`customfield_10257`] = strconv.FormatBool(fakeBool)
			comments = append(comments, fmt.Sprintf("Action: fake alarm is set to %t", fakeBool))
		}
		debugComments = append(debugComments, fmt.Sprintf("Event detail: %v", event))
		return true, comments, debugComments, nil
	} else {
		comments = append(comments, "no matched event happened recently")
		return false, comments, debugComments, nil
	}
}

// time range: (now-1h, now)
func filterByShortDurationEvents(activities []*clinic.Activity, events []string) *clinic.Activity {
	for _, activity := range activities {
		if includeEvent(events, activity.DisplayName) {
			return activity
		}
	}
	return nil
}

// time range: (now-1d, now-0.5h)
func filterByLongDurationEvents(activities []*clinic.Activity, events []string) *clinic.Activity {
	unclosedEvents := make(map[string]*clinic.Activity)
	for _, activity := range activities {
		if !includeEvent(events, activity.DisplayName) {
			continue
		}

		if strings.Contains(activity.DisplayName, "configchange") {
			if activity.DisplayName == "configchange:start" {
				unclosedEvents[activity.DisplayName] = activity
			} else if activity.DisplayName == "configchange:finish" {
				delete(unclosedEvents, "configchange:start")
			}
		} else {
			status, ok := activity.Tags["status"]
			if !ok {
				continue
			}
			if status == "started" {
				unclosedEvents[activity.DisplayName] = activity
			} else if status == "succeeded" {
				delete(unclosedEvents, activity.DisplayName)
			}
		}
	}
	if len(unclosedEvents) > 0 {
		for _, v := range unclosedEvents {
			return v
		}
	}
	return nil
}

func includeEvent(events []string, event string) bool {
	for _, v := range events {
		if v == event {
			return true
		}
		if strings.Contains(v, "configchange") && strings.Contains(event, "configchange") {
			return true
		}
	}
	return false
}
