package notify

import (
	"encoding/json"
	"time"

	"github.com/go-kit/kit/log"
	"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/clinic"
	"github.com/prometheus-community/jiralert/pkg/config"
	"github.com/prometheus-community/jiralert/pkg/locks"
	"github.com/prometheus-community/jiralert/pkg/notify/precheck"
	"github.com/prometheus-community/jiralert/pkg/notify/precheck/validate"
	"github.com/prometheus-community/jiralert/pkg/template"
)

// TODO(bwplotka): Consider renaming this package to ticketer.
const MaxStringLength = 5000

// Receiver wraps a specific Alertmanager receiver with its configuration and templates, creating/updating/reopening Jira issues based on Alertmanager notifications.
type Receiver struct {
	logger    log.Logger
	client    client.IssueTracker
	conf      *config.ReceiverConfig
	tmpl      *template.Template
	lks       *locks.Locks
	lkTimeout time.Duration
	clinicCli *clinic.Client
	matcher   precheck.MatcherInterface
	validate  validate.Validator

	timeNow func() time.Time
}

// NewReceiver creates a Receiver using the provided configuration, template and client.IssueTracker.
func NewReceiver(logger log.Logger,
	c *config.ReceiverConfig,
	t *template.Template,
	client client.IssueTracker,
	lks *locks.Locks,
	lkTimeout time.Duration,
	clinicCli *clinic.Client,
	matcher precheck.MatcherInterface,
	v validate.Validator,
) *Receiver {
	return &Receiver{
		logger:    logger,
		conf:      c,
		tmpl:      t,
		client:    client,
		timeNow:   time.Now,
		lks:       lks,
		lkTimeout: lkTimeout,
		clinicCli: clinicCli,
		matcher:   matcher,
		validate:  v,
	}
}

// Notify manages JIRA issues based on alertmanager webhook notify message.
func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, hashWithStartAt bool) (retry bool, err error) {
	project, err := r.tmpl.Execute(r.conf.Project, data)
	if err != nil {
		return false, errors.Wrap(err, "generate project from template")
	}

	if r.conf.DisableWarning {
		jsonData, _ := json.Marshal(data)
		level.Debug(r.logger).Log("raw data", string(jsonData))
		err = r.clinicCli.InsertAlertIntoTables(project, jsonData)
		if err != nil {
			level.Error(r.logger).Log("msg", "insert db error", "err", err)
		}
		return false, nil
	}

	issueGroupLabel := toGroupTicketLabel(data, hashJiraLabel, hashWithStartAt)

	issueSummary, issueDesc, err := r.gatherIssueInfo(data)
	if err != nil {
		return false, err
	}

	issue, retry, err := r.findToReuse(project, issueGroupLabel, issueSummary, issueDesc, data)
	if err != nil || issue != nil || len(data.Alerts.Firing()) == 0 {
		return retry, err
	}

	if r.conf.CreateOnly {
		level.Info(r.logger).Log("msg", "`create_only` is set, creating new issue", "label", issueGroupLabel)
	} else {
		level.Info(r.logger).Log("msg", "no recent matching issue found, creating new issue", "label", issueGroupLabel)
	}

	issueType, parent, labels, retry, err := r.aggregateByLabels(data, project, issueGroupLabel)
	if err != nil {
		return retry, err
	}

	newIssue, err := r.buildIssue(project, issueType, parent, labels, issueSummary, issueDesc, data)
	if err != nil {
		return false, err
	}

	comments, err := r.precheck(data, newIssue)
	if err != nil {
		level.Error(r.logger).Log("msg", "error when precheck alert", "err", err)
	}

	r.fillClusterAndTenant(newIssue, data)

	return r.createIssue(newIssue, comments)
}
