package notify

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"

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

func (r *Receiver) gatherIssueInfo(data *alertmanager.Data) (summary string, desc string, err error) {
	summary, err = r.tmpl.Execute(r.conf.Summary, data)
	if err != nil {
		return "", "", errors.Wrap(err, "generate summary from template")
	}
	desc, err = r.tmpl.Execute(r.conf.Description, data)
	if err != nil {
		return "", "", errors.Wrap(err, "render issue description")
	}
	return summary, desc, nil
}

func (r *Receiver) aggregateByLabels(data *alertmanager.Data, project, issueGroupLabel string) (issueType string, parent string, labels []string, retry bool, err error) {
	labels = append(labels, issueGroupLabel)

	customLabels := r.GetCustomGroupLabels(data)
	level.Info(r.logger).Log("msg", "custom aggregation labels", "labels", customLabels)
	if len(customLabels) > 0 {
		customGroupLabel := toCustomGroupTicketLabel(data, customLabels)

		if r.lks != nil {
			ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
			defer cancel()

			expiryTime := r.lkTimeout + time.Duration(int64(float64(r.lkTimeout)*0.1))
			level.Info(r.logger).Log("msg", "lock wait", "token", customGroupLabel, "expiry", expiryTime)

			lock, err := r.lks.LockWaitContext(ctx, customGroupLabel, expiryTime)
			if err != nil {
				level.Error(r.logger).Log("msg", "lock failed", "token", customGroupLabel, "err", err)
				return "", "", nil, true, err
			}
			level.Info(r.logger).Log("msg", "lock acquired", "token", customGroupLabel)

			defer func() {
				ok, err := lock.Unlock()
				if !ok || err != nil {
					if !ok && err == nil {
						level.Warn(r.logger).Log("msg", "may unlock expiried mutex")
						return
					}
					level.Error(r.logger).Log("msg", "unlock error", "err", err)
					return
				} else {
					level.Info(r.logger).Log("msg", "lock released", "token", customGroupLabel)
				}
			}()
		}

		issue, retry, err := r.findIssueLeader(project, customGroupLabel)
		if err != nil {
			return "", "", nil, retry, err
		}
		if issue != nil {
			parent = issue.Key
			issueType, err = r.tmpl.Execute(r.conf.SubTaskType, data)
			if err != nil {
				return "", "", nil, false, errors.Wrap(err, "render subtask type")
			}
		} else {
			labels = append(labels, customGroupLabel)
		}
	}

	if issueType == "" {
		issueType, err = r.tmpl.Execute(r.conf.IssueType, data)
		if err != nil {
			return "", "", nil, false, errors.Wrap(err, "render issue type")
		}
	}

	return issueType, parent, labels, false, nil
}

func (r *Receiver) buildIssue(project, issueType string, parent string, labels []string, issueSummary, issueDesc string, data *alertmanager.Data) (*client.Issue, error) {
	issue := &client.Issue{
		Project:      project,
		IssueType:    issueType,
		ParentKey:    parent,
		Description:  issueDesc,
		Summary:      issueSummary,
		Labels:       labels,
		CustomFields: make(map[string]interface{}),
	}
	if r.conf.Priority != "" {
		issuePrio, err := r.tmpl.Execute(r.conf.Priority, data)
		if err != nil {
			return nil, errors.Wrap(err, "render issue priority")
		}

		issue.Priority = issuePrio
	}

	if len(r.conf.Components) > 0 {
		issue.Components = make([]string, 0, len(r.conf.Components))
		for _, component := range r.conf.Components {
			issueComp, err := r.tmpl.Execute(component, data)
			if err != nil {
				return nil, errors.Wrap(err, "render issue component")
			}

			issue.Components = append(issue.Components, issueComp)
		}
	}

	if r.conf.AddGroupLabels {
		for k, v := range data.GroupLabels {
			issue.Labels = append(issue.Labels, fmt.Sprintf("%s=%q", k, v))
		}
	}

	jsonBytes, _ := json.Marshal(data)
	if len(jsonBytes) > MaxStringLength {
		r.logger.Log("msg", "the length of json data exceeds the max string length, it will be truncated", "length", len(jsonBytes), "max_length", MaxStringLength, "summary", issueSummary)
	}

	for key, value := range r.conf.Fields {
		var err error
		issue.CustomFields[key], err = deepCopyWithTemplate(value, r.tmpl, data)
		if err != nil {
			return nil, err
		}
	}

	return issue, nil
}

func (r *Receiver) precheck(data *alertmanager.Data, issue *client.Issue) (string, error) {
	targets, err := r.matcher.Match(data, r.conf)
	if err != nil {
		return "", errors.Wrap(err, "fail to match alert to prechecks")
	}
	var comments string
	if len(targets) == 1 {
		level.Debug(r.logger).Log("matched target", targets[0].Source)
		comments, err = r.validate.Validate(*targets[0], data, issue)
		if err != nil {
			return "", err
		}
	} else if len(targets) > 1 {
		comments = fmt.Sprintf("ERROR: more than one precheck target matched(targets: %v), please contact manager to check configuration.", targets)
		level.Error(r.logger).Log("msg", "more than one precheck target matched", "targets", targets)
	}
	return comments, nil
}

func (r *Receiver) fillClusterAndTenant(issue *client.Issue, data *alertmanager.Data) {
	if strings.Contains(issue.Summary, `KubePersistentVolumeFillingUp`) {
		issue.CustomFields[`customfield_10149`] = data.CommonLabels[`tidbcloud_cluster`]
	}
	if strings.Contains(issue.Summary, `DMExecutorNotRunning`) {
		issue.CustomFields[`customfield_10157`] = data.CommonLabels[`o11y_tenant_id`]
	}

	if issue.CustomFields[`customfield_10157`] == nil || issue.CustomFields[`customfield_10157`] == "" {
		if tenant, existTenant := data.CommonLabels[`tenant_id`]; existTenant {
			issue.CustomFields[`customfield_10157`] = tenant
		} else {
			if tenantID, existTenantID := data.CommonLabels[`o11y_tenant_id`]; existTenantID {
				issue.CustomFields[`customfield_10157`] = tenantID
			}
		}
	}

	if issue.CustomFields[`customfield_10149`] == nil || issue.CustomFields[`customfield_10149`] == "" {
		if cluster, existCluster := data.CommonLabels[`tidb_cluster_id`]; existCluster {
			issue.CustomFields[`customfield_10149`] = cluster
		}
	}

	if issue.CustomFields[`customfield_10149`] != nil {
		clusterIDValue := issue.CustomFields[`customfield_10149`].(string)
		if !isNumericString(clusterIDValue) {
			if namespace, existNamespace := data.CommonLabels[`namespace`]; existNamespace {
				if extractedID := extractNumberFromNamespace(namespace); extractedID != "" {
					issue.CustomFields[`customfield_10149`] = extractedID
				}
			}
		}
	}

	if r.clinicCli != nil {
		var clusterID, tenantID string
		if issue.CustomFields[`customfield_10157`] != nil {
			tenantID = issue.CustomFields[`customfield_10157`].(string)
		}
		if issue.CustomFields[`customfield_10149`] != nil {
			clusterID = issue.CustomFields[`customfield_10149`].(string)
		}
		level.Info(r.logger).Log("Issue cluster and tenant", issue.Key, "tenant", tenantID, "cluster", clusterID)

		if tenantID != "" || clusterID != "" {
			name, kind, err := r.clinicCli.GetKindNClusterNameByTenantIDAndClusterID(tenantID, clusterID)
			if err != nil {
				level.Warn(r.logger).Log("msg", "fail to kind and cluster name", "err", err)
			}
			issue.CustomFields[`customfield_10159`] = kind
			issue.CustomFields[`customfield_10162`] = name
		}
	}
}

func (r *Receiver) createIssue(issue *client.Issue, comments string) (bool, error) {
	i, retry, err := r.create(issue)
	if err != nil {
		return retry, err
	}
	if comments != "" {
		_, err = r.addComment(i.Key, comments)
		if err != nil {
			level.Error(r.logger).Log("msg", "comment error when appending", "key", issue.Key, "err", err)
		}
	}
	return false, nil
}

// deepCopyWithTemplate returns a deep copy of a map/slice/array/string/int/bool or combination thereof, executing the
// provided template (with the provided data) on all string keys or values. All maps are connverted to
// map[string]interface{}, with all non-string keys discarded.
func deepCopyWithTemplate(value interface{}, tmpl *template.Template, data interface{}) (interface{}, error) {
	if value == nil {
		return value, nil
	}

	valueMeta := reflect.ValueOf(value)
	switch valueMeta.Kind() {

	case reflect.String:
		res, err := tmpl.Execute(value.(string), data)
		if err != nil {
			return nil, err
		}
		if len(res) > MaxStringLength {
			res = res[:MaxStringLength]
		}
		return res, nil

	case reflect.Array, reflect.Slice:
		arrayLen := valueMeta.Len()
		converted := make([]interface{}, arrayLen)
		for i := 0; i < arrayLen; i++ {
			var err error
			converted[i], err = deepCopyWithTemplate(valueMeta.Index(i).Interface(), tmpl, data)
			if err != nil {
				return nil, err
			}
		}
		return converted, nil

	case reflect.Map:
		keys := valueMeta.MapKeys()
		converted := make(map[string]interface{}, len(keys))

		for _, keyMeta := range keys {
			var err error
			strKey, isString := keyMeta.Interface().(string)
			if !isString {
				continue
			}
			strKey, err = tmpl.Execute(strKey, data)
			if err != nil {
				return nil, err
			}
			converted[strKey], err = deepCopyWithTemplate(valueMeta.MapIndex(keyMeta).Interface(), tmpl, data)
			if err != nil {
				return nil, err
			}
		}
		return converted, nil
	default:
		return value, nil
	}
}
