package notifier

import (
	"errors"
	"fmt"
	"time"

	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	v2 "github.com/prometheus/alertmanager/api/v2"
	"github.com/prometheus/alertmanager/silence"
)

var (
	ErrGetSilencesInternal     = fmt.Errorf("unable to retrieve silence(s) due to an internal error")
	ErrDeleteSilenceInternal   = fmt.Errorf("unable to delete silence due to an internal error")
	ErrCreateSilenceBadPayload = fmt.Errorf("unable to create silence")
	ErrListSilencesBadPayload  = fmt.Errorf("unable to list silences")
	ErrSilenceNotFound         = silence.ErrNotFound
)

// ListSilences retrieves a list of stored silences. It supports a set of labels as filters.
func (am *Alertmanager) ListSilences(filter []string) (apimodels.GettableSilences, error) {
	matchers, err := parseFilter(filter)
	if err != nil {
		am.logger.Error("failed to parse matchers", "err", err)
		return nil, fmt.Errorf("%s: %w", ErrListSilencesBadPayload.Error(), err)
	}

	psils, _, err := am.silences.Query()
	if err != nil {
		am.logger.Error(ErrGetSilencesInternal.Error(), "err", err)
		return nil, fmt.Errorf("%s: %w", ErrGetSilencesInternal.Error(), err)
	}

	sils := apimodels.GettableSilences{}
	for _, ps := range psils {
		if !v2.CheckSilenceMatchesFilterLabels(ps, matchers) {
			continue
		}
		silence, err := v2.GettableSilenceFromProto(ps)
		if err != nil {
			am.logger.Error("unmarshaling from protobuf failed", "err", err)
			return apimodels.GettableSilences{}, fmt.Errorf("%s: failed to convert internal silence to API silence: %w",
				ErrGetSilencesInternal.Error(), err)
		}
		sils = append(sils, &silence)
	}

	v2.SortSilences(sils)

	return sils, nil
}

// GetSilence retrieves a silence by the provided silenceID. It returns ErrSilenceNotFound if the silence is not present.
func (am *Alertmanager) GetSilence(silenceID string) (apimodels.GettableSilence, error) {
	sils, _, err := am.silences.Query(silence.QIDs(silenceID))
	if err != nil {
		return apimodels.GettableSilence{}, fmt.Errorf("%s: %w", ErrGetSilencesInternal.Error(), err)
	}

	if len(sils) == 0 {
		am.logger.Error("failed to find silence", "err", err, "id", sils)
		return apimodels.GettableSilence{}, ErrSilenceNotFound
	}

	sil, err := v2.GettableSilenceFromProto(sils[0])
	if err != nil {
		am.logger.Error("unmarshaling from protobuf failed", "err", err)
		return apimodels.GettableSilence{}, fmt.Errorf("%s: failed to convert internal silence to API silence: %w",
			ErrGetSilencesInternal.Error(), err)
	}

	return sil, nil
}

// CreateSilence persists the provided silence and returns the silence ID if successful.
func (am *Alertmanager) CreateSilence(ps *apimodels.PostableSilence) (string, error) {
	sil, err := v2.PostableSilenceToProto(ps)
	if err != nil {
		am.logger.Error("marshaling to protobuf failed", "err", err)
		return "", fmt.Errorf("%s: failed to convert API silence to internal silence: %w",
			ErrCreateSilenceBadPayload.Error(), err)
	}

	if sil.StartsAt.After(sil.EndsAt) || sil.StartsAt.Equal(sil.EndsAt) {
		msg := "start time must be before end time"
		am.logger.Error(msg, "err", "starts_at", sil.StartsAt, "ends_at", sil.EndsAt)
		return "", fmt.Errorf("%s: %w", msg, ErrCreateSilenceBadPayload)
	}

	if sil.EndsAt.Before(time.Now()) {
		msg := "end time can't be in the past"
		am.logger.Error(msg, "ends_at", sil.EndsAt)
		return "", fmt.Errorf("%s: %w", msg, ErrCreateSilenceBadPayload)
	}

	silenceID, err := am.silences.Set(sil)
	if err != nil {
		am.logger.Error("msg", "unable to save silence", "err", err)
		if errors.Is(err, silence.ErrNotFound) {
			return "", ErrSilenceNotFound
		}
		return "", fmt.Errorf("unable to save silence: %s: %w", err.Error(), ErrCreateSilenceBadPayload)
	}

	return silenceID, nil
}

// DeleteSilence looks for and expires the silence by the provided silenceID. It returns ErrSilenceNotFound if the silence is not present.
func (am *Alertmanager) DeleteSilence(silenceID string) error {
	if err := am.silences.Expire(silenceID); err != nil {
		if errors.Is(err, silence.ErrNotFound) {
			return ErrSilenceNotFound
		}
		return fmt.Errorf("%s: %w", err.Error(), ErrDeleteSilenceInternal)
	}

	return nil
}
