package notify

import (
	"fmt"
	"strings"
	"time"

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

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

func (r *Receiver) findToReuse(project, issueGroupLabel, issueSummary, issueDesc string, data *alertmanager.Data) (*client.Issue, bool, error) {
	issue, retry, err := r.findIssueToReuse(project, issueGroupLabel)
	if err != nil {
		return nil, retry, err
	}

	if !r.conf.CreateOnly && issue != nil {
		commentChangelog := func(comment string) {
			if r.conf.CommentChangelog {
				_, err := r.addComment(issue.Key, comment)
				if err != nil {
					level.Error(r.logger).Log("msg", "comment error", "key", issue.Key, "err", err)
				}
			}
		}

		now := r.timeNow()
		beijing, _ := time.LoadLocation("Asia/Shanghai")
		pacific, _ := time.LoadLocation("America/Los_Angeles")
		timestamp := fmt.Sprintf("[%s | %s]", now.In(beijing).Format("2006-01-02 15:04:05 -07"), now.In(pacific).Format("2006-01-02 15:04:05 -07"))

		headline := "Alert event received."
		var commentLines []string
		hasChanges := false

		defer func() {
			parts := []string{timestamp, headline}
			parts = append(parts, commentLines...)
			if hasChanges || err == nil {
				commentChangelog(strings.Join(parts, "\n"))
			}
		}()

		if issue.Summary != issueSummary {
			headline += " Summary updated."
			hasChanges = true
			level.Debug(r.logger).Log("msg", "update summary", "from", issue.Summary, "to", issueSummary)
			retry, err := r.updateSummary(issue.Key, issueSummary)
			if err != nil {
				return issue, retry, err
			}
		}

		if issue.Description != issueDesc {
			headline += " Description updated."
			hasChanges = true
			level.Debug(r.logger).Log("msg", "update description", "from", issue.Description, "to", issueDesc)
			retry, err := r.updateDescription(issue.Key, issueDesc)
			if err != nil {
				return issue, retry, err
			}
		}

		// Append instance details to comment
		if firing := data.Alerts.Firing(); len(firing) > 0 {
			commentLines = append(commentLines, "Firing: "+formatAlertInstances(firing))
		}
		if recovered := data.Alerts.Resolved(); len(recovered) > 0 {
			commentLines = append(commentLines, "Recovered: "+formatAlertInstances(recovered))
		}

		issueIsDone := issue.StatusCategory == "done"

		if len(data.Alerts.Firing()) == 0 {
			if !issueIsDone && r.conf.AutoResolve != nil {
				level.Debug(r.logger).Log("msg", "no firing alert; resolving issue", "key", issue.Key, "label", issueGroupLabel)
				retry, err := r.AutoRecover(issue.Key, `{panel:title=YES|borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1|bgColor=#FFFFCE}`)
				if err != nil {
					return issue, retry, err
				}
				commentLines = append(commentLines, "No firing alert, this alert has been auto recovered. Please check this issue to confirm there is no potential risk.")
				hasChanges = true
				return issue, false, nil
			}

			level.Debug(r.logger).Log("msg", "no firing alert; summary checked, nothing else to do.", "key", issue.Key, "label", issueGroupLabel)
			return issue, false, nil
		}

		if !issueIsDone {
			level.Debug(r.logger).Log("msg", "issue is unresolved, all is done", "key", issue.Key, "label", issueGroupLabel)
			return issue, false, nil
		}

		// Issue is done, create a new ticket instead of reopening
		level.Info(r.logger).Log("msg", "issue is closed, will create new ticket", "key", issue.Key, "label", issueGroupLabel)
		return nil, false, nil
	}

	if len(data.Alerts.Firing()) == 0 {
		level.Debug(r.logger).Log("msg", "no firing alert; nothing to do.", "label", issueGroupLabel)
		return nil, false, nil
	}

	return nil, false, nil
}

func (r *Receiver) search(project, issueLabel string, orderBy string) (*client.Issue, bool, error) {
	query := fmt.Sprintf("project=\"%s\" and labels=%q order by %s desc", project, issueLabel, orderBy)
	options := &client.SearchOptions{
		MaxResults: 2,
	}

	level.Debug(r.logger).Log("msg", "search", "query", query, "options", fmt.Sprintf("%+v", options))
	issues, retry, err := r.client.Search(query, options)
	if err != nil {

		return nil, retry, err
	}

	if len(issues) == 0 {
		level.Debug(r.logger).Log("msg", "no results", "query", query)
		return nil, false, nil
	}

	issue := issues[0]
	if len(issues) > 1 {
		level.Warn(r.logger).Log("msg", "more than one issue matched, picking most recent", "query", query, "issues", issues, "picked", issue)
	}

	level.Debug(r.logger).Log("msg", "found", "issue", issue, "query", query)
	return issue, false, nil
}

func (r *Receiver) findIssueToReuse(project string, issueGroupLabel string) (*client.Issue, bool, error) {
	if r.conf.CreateOnly {
		return nil, false, nil
	}
	issue, retry, err := r.search(project, issueGroupLabel, "created")
	if err != nil {
		return nil, retry, err
	}

	if issue == nil {
		return nil, false, nil
	}

	createdTime := issue.CreatedDate
	if createdTime != (time.Time{}) && createdTime.Add(time.Duration(*r.conf.ReopenDuration)).Before(r.timeNow()) && *r.conf.ReopenDuration != 0 {
		level.Debug(r.logger).Log("msg", "existing issue is too old to reuse, skipping", "key", issue.Key, "label", issueGroupLabel, "created_time", createdTime.Format(time.RFC3339), "reopen_duration", *r.conf.ReopenDuration)
		return nil, false, nil
	}

	// Reuse issue.
	return issue, false, nil
}

func (r *Receiver) findIssueLeader(project string, customGroupLabel string) (*client.Issue, bool, error) {
	if r.conf.CreateOnly {
		return nil, false, nil
	}
	issue, retry, err := r.search(project, customGroupLabel, "created")
	if err != nil {
		return nil, retry, err
	}

	if issue == nil {
		return nil, false, nil
	}

	if issue.StatusCategory == "done" {
		level.Debug(r.logger).Log("msg", "existing issue leader is done, skipping", "key", issue.Key, "label", customGroupLabel)
		return nil, false, nil
	}

	createdTime := issue.CreatedDate
	if r.conf.CustomGroupDuration != nil &&
		createdTime != (time.Time{}) &&
		createdTime.Add(time.Duration(*r.conf.CustomGroupDuration)).Before(r.timeNow()) &&
		*r.conf.CustomGroupDuration != 0 {

		level.Debug(r.logger).Log("msg", "existing issue leader is too old, skipping", "key", issue.Key, "label", customGroupLabel, "created_time", createdTime.Format(time.RFC3339), "custom_group_duration", *r.conf.CustomGroupDuration)
		return nil, false, nil
	}

	return issue, false, nil
}

func (r *Receiver) updateSummary(issueKey string, summary string) (bool, error) {
	level.Debug(r.logger).Log("msg", "updating issue with new summary", "key", issueKey, "summary", summary)

	retry, err := r.client.UpdateSummary(issueKey, summary)
	if err != nil {
		return retry, err
	}
	level.Debug(r.logger).Log("msg", "issue summary updated", "key", issueKey)
	return false, nil
}

func (r *Receiver) updateDescription(issueKey string, description string) (bool, error) {
	level.Debug(r.logger).Log("msg", "updating issue with new description", "key", issueKey, "description", description)

	retry, err := r.client.UpdateDescription(issueKey, description)
	if err != nil {
		return retry, err
	}
	level.Debug(r.logger).Log("msg", "issue description updated", "key", issueKey)
	return false, nil
}

func (r *Receiver) create(issue *client.Issue) (*client.Issue, bool, error) {
	level.Debug(r.logger).Log("msg", "create", "issue", fmt.Sprintf("%+v", issue))
	newIssue, retry, err := r.client.Create(issue)
	if err != nil {
		return nil, retry, err
	}
	issue = newIssue

	level.Info(r.logger).Log("msg", "issue created", "key", issue.Key, "id", issue.ID)
	return issue, false, nil
}

func (r *Receiver) addComment(issueKey string, body string) (bool, error) {
	level.Debug(r.logger).Log("msg", "add comment", "comment", body)
	retry, err := r.client.AddComment(issueKey, body)
	if err != nil {
		return retry, err
	}
	level.Debug(r.logger).Log("msg", "comment added", "key", issueKey)
	return false, nil
}

func (r *Receiver) AutoRecover(issueKey string, recovered string) (bool, error) {
	level.Debug(r.logger).Log("msg", "auto recover", "issue key", issueKey)
	return r.client.AutoRecover(issueKey, recovered)
}

// instanceLabelPriority defines the priority order for extracting instance identifiers from alert labels.
var instanceLabelPriority = []string{"pod", "instance", "persistentvolumeclaim", "store", "task_name", "name"}

func extractInstanceID(labels alertmanager.KV) string {
	for _, key := range instanceLabelPriority {
		if v, ok := labels[key]; ok && v != "" {
			return v
		}
	}
	return ""
}

func formatAlertInstances(alerts []alertmanager.Alert) string {
	var ids []string
	for _, a := range alerts {
		if id := extractInstanceID(a.Labels); id != "" {
			ids = append(ids, id)
		}
	}
	if len(ids) > 0 {
		return strings.Join(ids, ", ")
	}
	return fmt.Sprintf("%d alerts", len(alerts))
}
