package definitions

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

	"github.com/go-openapi/strfmt"
	amv2 "github.com/prometheus/alertmanager/api/v2/models"
	"github.com/prometheus/alertmanager/config"
	"github.com/prometheus/alertmanager/pkg/labels"
	"github.com/prometheus/common/model"
	"go.yaml.in/yaml/v3"

	"github.com/grafana/alerting/definition"
	alertingmodels "github.com/grafana/alerting/models"

	"github.com/grafana/grafana/pkg/apimachinery/errutil"
)

// swagger:route POST /alertmanager/{DatasourceUID}/config/api/v1/alerts alertmanager RoutePostAlertingConfig
//
// sets an Alerting config
//
//     Responses:
//       201: Ack
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/config/api/v1/alerts alertmanager RouteGetGrafanaAlertingConfig
//
// gets an Alerting config
//
// This API is designated to internal use only and can be removed or changed at any time without prior notice.
//
// Deprecated: true
//
//     Responses:
//       200: GettableUserConfig
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/config/api/v1/alerts alertmanager RouteGetAlertingConfig
//
// gets an Alerting config
//
//     Responses:
//       200: GettableUserConfig
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/config/history alertmanager RouteGetGrafanaAlertingConfigHistory
//
// gets Alerting configurations that were successfully applied in the past
//
// This API is designated to internal use only and can be removed or changed at any time without prior notice.
//
// Deprecated: true
//     Responses:
//       200: GettableHistoricUserConfigs

// swagger:route POST /alertmanager/grafana/config/history/{id}/_activate alertmanager RoutePostGrafanaAlertingConfigHistoryActivate
//
// revert Alerting configuration to the historical configuration specified by the given id
//
// This API is designated to internal use only and can be removed or changed at any time without prior notice.
//
// Deprecated: true
//     Responses:
//       202: Ack
//       400: ValidationError
//       404: NotFound

// swagger:route DELETE /alertmanager/grafana/config/api/v1/alerts alertmanager RouteDeleteGrafanaAlertingConfig
//
// deletes the Alerting config for a tenant
//
// This API is designated to internal use only and can be removed or changed at any time without prior notice.
//
// Deprecated: true
//     Responses:
//       200: Ack
//       400: ValidationError

// swagger:route DELETE /alertmanager/{DatasourceUID}/config/api/v1/alerts alertmanager RouteDeleteAlertingConfig
//
// deletes the Alerting config for a tenant
//
//     Responses:
//       200: Ack
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/api/v2/status alertmanager RouteGetGrafanaAMStatus
//
// get alertmanager status and configuration
//
//     Responses:
//       200: GettableStatus
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/api/v2/status alertmanager RouteGetAMStatus
//
// get alertmanager status and configuration
//
//     Responses:
//       200: GettableStatus
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/api/v2/alerts alertmanager RouteGetGrafanaAMAlerts
//
// get alertmanager alerts
//
//     Responses:
//       200: gettableAlerts
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/api/v2/alerts alertmanager RouteGetAMAlerts
//
// get alertmanager alerts
//
//     Responses:
//       200: gettableAlerts
//       400: ValidationError
//       404: NotFound

// swagger:route POST /alertmanager/{DatasourceUID}/api/v2/alerts alertmanager RoutePostAMAlerts
//
// create alertmanager alerts
//
//     Responses:
//       200: Ack
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/api/v2/alerts/groups alertmanager RouteGetGrafanaAMAlertGroups
//
// get alertmanager alerts
//
//     Responses:
//       200: alertGroups
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/api/v2/alerts/groups alertmanager RouteGetAMAlertGroups
//
// get alertmanager alerts
//
//     Responses:
//       200: alertGroups
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/config/api/v1/receivers alertmanager RouteGetGrafanaReceivers
//
// Get a list of all receivers
//
//     Responses:
//       200: receiversResponse

// swagger:route POST /alertmanager/grafana/config/api/v1/receivers/test alertmanager RoutePostTestGrafanaReceivers
//
// Test Grafana managed receivers without saving them.
//
//     Responses:
//
//       200: Ack
//       207: MultiStatus
//       400: ValidationError
//       403: PermissionDenied
//       404: NotFound
//       408: Failure
//       409: AlertManagerNotReady

// swagger:route POST /alertmanager/grafana/config/api/v1/templates/test alertmanager RoutePostTestGrafanaTemplates
//
// Test Grafana managed templates without saving them.
//     Produces:
//     - application/json
//
//     Responses:
//
//       200: TestTemplatesResults
//       400: ValidationError
//       403: PermissionDenied
//       409: AlertManagerNotReady

// swagger:route GET /alertmanager/grafana/api/v2/silences alertmanager RouteGetGrafanaSilences
//
// get silences
//
//     Responses:
//       200: gettableGrafanaSilences
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/api/v2/silences alertmanager RouteGetSilences
//
// get silences
//
//     Responses:
//       200: gettableSilences
//       400: ValidationError
//       404: NotFound

// swagger:route POST /alertmanager/grafana/api/v2/silences alertmanager RouteCreateGrafanaSilence
//
// create silence
//
//     Responses:
//       202: postSilencesOKBody
//       400: ValidationError

// swagger:route POST /alertmanager/{DatasourceUID}/api/v2/silences alertmanager RouteCreateSilence
//
// create silence
//
//     Responses:
//       201: postSilencesOKBody
//       400: ValidationError
//       404: NotFound

// swagger:route GET /alertmanager/grafana/api/v2/silence/{SilenceId} alertmanager RouteGetGrafanaSilence
//
// get silence
//
//     Responses:
//       200: gettableGrafanaSilence
//       400: ValidationError

// swagger:route GET /alertmanager/{DatasourceUID}/api/v2/silence/{SilenceId} alertmanager RouteGetSilence
//
// get silence
//
//     Responses:
//       200: gettableSilence
//       400: ValidationError
//       404: NotFound

// swagger:route DELETE /alertmanager/grafana/api/v2/silence/{SilenceId} alertmanager RouteDeleteGrafanaSilence
//
// delete silence
//
//     Responses:
//       200: Ack
//       400: ValidationError

// swagger:route DELETE /alertmanager/{DatasourceUID}/api/v2/silence/{SilenceId} alertmanager RouteDeleteSilence
//
// delete silence
//
//     Responses:
//       200: Ack
//       400: ValidationError
//       404: NotFound

// Alias all the needed Alertmanager types, functions and constants so that they can be imported directly from grafana/alerting
// without having to modify any of the usage within Grafana.
type (
	Config                    = definition.Config
	Route                     = definition.Route
	PostableGrafanaReceiver   = definition.PostableGrafanaReceiver
	PostableApiAlertingConfig = definition.PostableApiAlertingConfig
	RawMessage                = definition.RawMessage
	Provenance                = definition.Provenance
	ObjectMatchers            = definition.ObjectMatchers
	PostableApiReceiver       = definition.PostableApiReceiver
	PostableGrafanaReceivers  = definition.PostableGrafanaReceivers
)

type MergeResult definition.MergeResult

func (m MergeResult) LogContext() []any {
	if len(m.RenamedReceivers) == 0 && len(m.RenamedTimeIntervals) == 0 {
		return nil
	}
	logCtx := make([]any, 0, 4)
	if len(m.RenamedReceivers) > 0 {
		rcvBuilder := strings.Builder{}
		for from, to := range m.RenamedReceivers {
			rcvBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to))
		}
		logCtx = append(logCtx, "renamedReceivers", fmt.Sprintf("[%s]", rcvBuilder.String()[0:rcvBuilder.Len()-1]))
	}
	if len(m.RenamedTimeIntervals) > 0 {
		intervalBuilder := strings.Builder{}
		for from, to := range m.RenamedTimeIntervals {
			intervalBuilder.WriteString(fmt.Sprintf("'%s'->'%s',", from, to))
		}
		logCtx = append(logCtx, "renamedTimeIntervals", fmt.Sprintf("[%s]", intervalBuilder.String()[0:intervalBuilder.Len()-1]))
	}
	return logCtx
}

const (
	errInvalidExtraConfigurationMsg = "Invalid Alertmanager configuration: {{.Public.Error}}"
)

var (
	errInvalidExtraConfigurationBase = errutil.ValidationFailed("alerting.invalidExtraConfiguration").MustTemplate(errInvalidExtraConfigurationMsg, errutil.WithPublic(errInvalidExtraConfigurationMsg))
)

func errInvalidExtraConfiguration(err error) error {
	return errInvalidExtraConfigurationBase.Build(errutil.TemplateData{Public: map[string]any{"Error": err}})
}

var (
	AsGrafanaRoute = definition.AsGrafanaRoute
	AllReceivers   = definition.AllReceivers
)

// swagger:model
type PermissionDenied struct{}

// swagger:model
type AlertManagerNotReady struct{}

// swagger:model
type MultiStatus struct{}

// swagger:parameters RouteGetGrafanaAlertingConfigHistory
type RouteGetGrafanaAlertingConfigHistoryParams struct {
	// Limit response to n historic configurations.
	// in:query
	Limit int `json:"limit"`
}

// swagger:parameters RoutePostTestGrafanaReceivers
type TestReceiversConfigParams struct {
	// in:body
	Body TestReceiversConfigBodyParams
}

type TestReceiversConfigBodyParams struct {
	Alert     *TestReceiversConfigAlertParams `yaml:"alert,omitempty" json:"alert,omitempty"`
	Receivers []*PostableApiReceiver          `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}

type TestReceiversConfigAlertParams struct {
	Annotations model.LabelSet `yaml:"annotations,omitempty" json:"annotations,omitempty"`
	Labels      model.LabelSet `yaml:"labels,omitempty" json:"labels,omitempty"`
}

// swagger:model
type TestReceiversResult struct {
	Alert      TestReceiversConfigAlertParams `json:"alert"`
	Receivers  []TestReceiverResult           `json:"receivers"`
	NotifiedAt time.Time                      `json:"notified_at"`
}

// swagger:model
type TestReceiverResult struct {
	Name    string                     `json:"name"`
	Configs []TestReceiverConfigResult `json:"grafana_managed_receiver_configs"`
}

// swagger:model
type TestReceiverConfigResult struct {
	Name   string `json:"name"`
	UID    string `json:"uid"`
	Status string `json:"status"`
	Error  string `json:"error,omitempty"`
}

// swagger:parameters RoutePostTestGrafanaTemplates
type TestTemplatesConfigParams struct {
	// in:body
	Body TestTemplatesConfigBodyParams
}

type TestTemplatesConfigBodyParams struct {
	// Alerts to use as data when testing the template.
	Alerts []*amv2.PostableAlert `json:"alerts"`

	// Template string to test.
	Template string `json:"template"`

	// Name of the template file.
	Name string `json:"name"`
}

// swagger:model
type TestTemplatesResults struct {
	Results []TestTemplatesResult      `json:"results,omitempty"`
	Errors  []TestTemplatesErrorResult `json:"errors,omitempty"`
}

type TestTemplatesResult struct {
	// Name of the associated template definition for this result.
	Name string `json:"name"`

	// Interpolated value of the template.
	Text string `json:"text"`

	// Scope that was successfully used to interpolate the template. If the root scope "." fails, more specific
	// scopes will be tried, such as ".Alerts', or ".Alert".
	Scope TemplateScope `json:"scope"`
}

type TestTemplatesErrorResult struct {
	// Name of the associated template for this error. Will be empty if the Kind is "invalid_template".
	Name string `json:"name,omitempty"`

	// Kind of template error that occurred.
	Kind TemplateErrorKind `json:"kind"`

	// Error message.
	Message string `json:"message"`
}

// swagger:enum TemplateErrorKind
type TemplateErrorKind string

const (
	InvalidTemplate TemplateErrorKind = "invalid_template"
	ExecutionError  TemplateErrorKind = "execution_error"
)

// swagger:enum TemplateScope
type TemplateScope string

const (
	RootScope   TemplateScope = "."
	AlertsScope TemplateScope = ".Alerts"
	AlertScope  TemplateScope = ".Alert"
)

// swagger:parameters RouteCreateSilence RouteCreateGrafanaSilence
type CreateSilenceParams struct {
	// in:body
	Silence PostableSilence
}

// swagger:parameters RouteGetSilence RouteDeleteSilence RouteGetGrafanaSilence RouteDeleteGrafanaSilence
type GetDeleteSilenceParams struct {
	// in:path
	SilenceId string
}

// swagger:parameters RouteGetSilences RouteGetGrafanaSilences
type GetSilencesParams struct {
	// in:query
	Filter []string `json:"filter"`
	// Return rule metadata with silence.
	// in:query
	// required:false
	RuleMetadata bool `json:"ruleMetadata"`
	// Return access control metadata with silence.
	// in:query
	// required:false
	AccessControl bool `json:"accesscontrol"`
}

// swagger:model
type GettableStatus struct {
	// cluster
	// Required: true
	Cluster *amv2.ClusterStatus `json:"cluster"`

	// config
	// Required: true
	Config *PostableApiAlertingConfig `json:"config"`

	// uptime
	// Required: true
	// Format: date-time
	Uptime *strfmt.DateTime `json:"uptime"`

	// version info
	// Required: true
	VersionInfo *amv2.VersionInfo `json:"versionInfo"`
}

func (s *GettableStatus) UnmarshalJSON(b []byte) error {
	amStatus := amv2.AlertmanagerStatus{}
	if err := json.Unmarshal(b, &amStatus); err != nil {
		return err
	}

	c := config.Config{}
	if err := yaml.Unmarshal([]byte(*amStatus.Config.Original), &c); err != nil {
		return err
	}

	s.Cluster = amStatus.Cluster
	s.Config = &PostableApiAlertingConfig{Config: Config{
		Global:            c.Global,
		Route:             AsGrafanaRoute(c.Route),
		InhibitRules:      c.InhibitRules,
		Templates:         c.Templates,
		MuteTimeIntervals: c.MuteTimeIntervals,
		TimeIntervals:     c.TimeIntervals,
	}}
	s.Uptime = amStatus.Uptime
	s.VersionInfo = amStatus.VersionInfo

	type overrides struct {
		Receivers *[]*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
	}

	if err := yaml.Unmarshal([]byte(*amStatus.Config.Original), &overrides{Receivers: &s.Config.Receivers}); err != nil {
		return err
	}

	return nil
}

func NewGettableStatus(cfg *PostableApiAlertingConfig) *GettableStatus {
	// In Grafana, the only field we support is Config.
	cs := amv2.ClusterStatusStatusDisabled
	na := "N/A"
	return &GettableStatus{
		Cluster: &amv2.ClusterStatus{
			Status: &cs,
			Peers:  []*amv2.PeerStatus{},
		},
		VersionInfo: &amv2.VersionInfo{
			Branch:    &na,
			BuildDate: &na,
			BuildUser: &na,
			GoVersion: &na,
			Revision:  &na,
			Version:   &na,
		},
		Config: cfg,
	}
}

type PostableSilence = amv2.PostableSilence

// swagger:model postSilencesOKBody
type PostSilencesOKBody struct { // vendored from "github.com/prometheus/alertmanager/api/v2/restapi/operations/silence/PostSilencesOKBody" because import brings too many other things
	// silence ID
	SilenceID string `json:"silenceID,omitempty"`
}

// GettableSilences gettable silences
//
// swagger:model gettableSilences
type GettableSilences = amv2.GettableSilences

type GettableSilence = amv2.GettableSilence

// swagger:model gettableGrafanaSilence
type GettableGrafanaSilence struct {
	*GettableSilence `json:",inline"`
	Metadata         *SilenceMetadata `json:"metadata,omitempty"`
	// example: {"read": true, "write": false, "create": false}
	Permissions map[SilencePermission]bool `json:"accessControl,omitempty"`
}

type SilenceMetadata struct {
	RuleUID   string `json:"rule_uid,omitempty"`
	RuleTitle string `json:"rule_title,omitempty"`
	FolderUID string `json:"folder_uid,omitempty"`
}

type SilencePermission string

const (
	SilencePermissionRead   SilencePermission = "read"
	SilencePermissionCreate SilencePermission = "create"
	SilencePermissionWrite  SilencePermission = "write"
)

// Correctly embed the GettableSilence into the GettableGrafanaSilence struct. This is needed because GettableSilence
// has a custom UnmarshalJSON method.
func (s GettableGrafanaSilence) MarshalJSON() ([]byte, error) {
	gettable, err := json.Marshal(s.GettableSilence)
	if err != nil {
		return nil, err
	}

	var data map[string]interface{}
	if err := json.Unmarshal(gettable, &data); err != nil {
		return nil, err
	}

	if s.Metadata != nil {
		data["metadata"] = s.Metadata
	}

	if s.Permissions != nil {
		data["accessControl"] = s.Permissions
	}

	return json.Marshal(data)
}

// swagger:model gettableGrafanaSilences
type GettableGrafanaSilences []*GettableGrafanaSilence

// GettableAlerts gettable alerts
//
// swagger:model gettableAlerts
type GettableAlerts = amv2.GettableAlerts

type GettableAlert = amv2.GettableAlert

// AlertGroups alert groups
//
// swagger:model alertGroups
type AlertGroups = amv2.AlertGroups

type AlertGroup = amv2.AlertGroup

type Receiver = alertingmodels.ReceiverStatus

// swagger:response receiversResponse
type ReceiversResponse struct {
	// in:body
	Body []alertingmodels.ReceiverStatus
}

type Integration = alertingmodels.IntegrationStatus

// swagger:parameters RouteGetAMAlerts RouteGetAMAlertGroups RouteGetGrafanaAMAlerts RouteGetGrafanaAMAlertGroups
type AlertsParams struct {

	// Show active alerts
	// in: query
	// required: false
	// default: true
	Active bool `json:"active"`

	// Show silenced alerts
	// in: query
	// required: false
	// default: true
	Silenced bool `json:"silenced"`

	// Show inhibited alerts
	// in: query
	// required: false
	// default: true
	Inhibited bool `json:"inhibited"`

	// A list of matchers to filter alerts by
	// in: query
	// required: false
	Matchers []string `json:"filter"`

	// A regex matching receivers to filter alerts by
	// in: query
	// required: false
	Receivers string `json:"receiver"`
}

// swagger:parameters RoutePostAMAlerts
type PostableAlerts struct {
	// in:body
	PostableAlerts []amv2.PostableAlert `yaml:"" json:""`
}

// swagger:parameters RoutePostAlertingConfig RoutePostGrafanaAlertingConfig
type BodyAlertingConfig struct {
	// in:body
	Body PostableUserConfig
}

// swagger:parameters RoutePostGrafanaAlertingConfigHistoryActivate
type HistoricalConfigId struct {
	// Id should be the id of the GettableHistoricUserConfig
	// in:path
	Id int64 `json:"id"`
}

// alertmanager routes
// swagger:parameters RoutePostAlertingConfig RouteGetAlertingConfig RouteDeleteAlertingConfig RouteGetAMStatus RouteGetAMAlerts RoutePostAMAlerts RouteGetAMAlertGroups RouteGetSilences RouteCreateSilence RouteGetSilence RouteDeleteSilence RoutePostAlertingConfig
// testing routes
// swagger:parameters RouteTestRuleConfig
// prom routes
// swagger:parameters RouteGetRuleStatuses RouteGetAlertStatuses
// ruler routes
// swagger:parameters RouteGetRulesConfig RoutePostNameRulesConfig RouteGetNamespaceRulesConfig RouteDeleteNamespaceRulesConfig RouteGetRulegGroupConfig RouteDeleteRuleGroupConfig
type DatasourceUIDReference struct {
	// DatasoureUID should be the datasource UID identifier
	// in:path
	DatasourceUID string
}

type ExtraConfiguration struct {
	Identifier         string            `yaml:"identifier" json:"identifier"`
	MergeMatchers      config.Matchers   `yaml:"merge_matchers" json:"merge_matchers"`
	TemplateFiles      map[string]string `yaml:"template_files" json:"template_files"`
	AlertmanagerConfig string            `yaml:"alertmanager_config" json:"alertmanager_config"`
}

func (c *ExtraConfiguration) parsePrometheusConfig() (config.Config, error) {
	if c.AlertmanagerConfig == "" {
		return config.Config{}, fmt.Errorf("no alertmanager configuration available")
	}

	var prometheusConfig config.Config
	if err := yaml.Unmarshal([]byte(c.AlertmanagerConfig), &prometheusConfig); err != nil {
		return config.Config{}, fmt.Errorf("failed to parse alertmanager config: %w", err)
	}

	return prometheusConfig, nil
}

func (c *ExtraConfiguration) GetAlertmanagerConfig() (PostableApiAlertingConfig, error) {
	prometheusConfig, err := c.parsePrometheusConfig()
	if err != nil {
		return PostableApiAlertingConfig{}, err
	}

	return fromPrometheusConfig(prometheusConfig), nil
}

// GetSanitizedAlertmanagerConfigYAML returns the alertmanager configuration as a YAML string
// with secrets masked and global settings removed for mimirtool compatibility.
func (c *ExtraConfiguration) GetSanitizedAlertmanagerConfigYAML() (string, error) {
	prometheusConfig, err := c.parsePrometheusConfig()
	if err != nil {
		return "", err
	}

	configYAML, err := yaml.Marshal(prometheusConfig)
	if err != nil {
		return "", fmt.Errorf("failed to marshal sanitized configuration: %w", err)
	}

	return string(configYAML), nil
}

func (c ExtraConfiguration) Validate() error {
	if c.Identifier == "" {
		return errors.New("identifier is required")
	}

	if len(c.MergeMatchers) == 0 {
		return errInvalidExtraConfiguration(errors.New("at least one matcher is required"))
	}

	for _, m := range c.MergeMatchers {
		if m.Type != labels.MatchEqual {
			return errInvalidExtraConfiguration(errors.New("only matchers with type equal are supported"))
		}
	}

	cfg, err := c.GetAlertmanagerConfig()
	if err != nil {
		return errInvalidExtraConfiguration(fmt.Errorf("failed to parse alertmanager config: %w", err))
	}
	err = cfg.Validate()
	if err != nil {
		return errInvalidExtraConfiguration(fmt.Errorf("invalid alertmanager config: %w", err))
	}

	return nil
}

func fromPrometheusConfig(prometheusConfig config.Config) PostableApiAlertingConfig {
	config := PostableApiAlertingConfig{
		Config: Config{
			Global:            prometheusConfig.Global,
			Route:             AsGrafanaRoute(prometheusConfig.Route),
			InhibitRules:      prometheusConfig.InhibitRules,
			TimeIntervals:     prometheusConfig.TimeIntervals,
			MuteTimeIntervals: prometheusConfig.MuteTimeIntervals,
			Templates:         prometheusConfig.Templates,
		},
	}

	for _, receiver := range prometheusConfig.Receivers {
		config.Receivers = append(config.Receivers, &PostableApiReceiver{
			Receiver: receiver,
		})
	}

	return config
}

// swagger:model
type PostableUserConfig struct {
	TemplateFiles      map[string]string         `yaml:"template_files" json:"template_files"`
	AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
	ExtraConfigs       []ExtraConfiguration      `yaml:"extra_config,omitempty" json:"extra_config,omitempty"`
	amSimple           map[string]interface{}    `yaml:"-" json:"-"`
}

func (c *PostableUserConfig) GetMergedAlertmanagerConfig() (MergeResult, error) {
	if len(c.ExtraConfigs) == 0 {
		return MergeResult{
			Config: c.AlertmanagerConfig,
		}, nil
	}
	// support only one config for now
	mimirCfg := c.ExtraConfigs[0]
	opts := definition.MergeOpts{
		DedupSuffix:     mimirCfg.Identifier,
		SubtreeMatchers: mimirCfg.MergeMatchers,
	}
	if err := opts.Validate(); err != nil {
		return MergeResult{}, fmt.Errorf("invalid merge options: %w", err)
	}

	mcfg, err := mimirCfg.GetAlertmanagerConfig()
	if err != nil {
		return MergeResult{}, fmt.Errorf("failed to get mimir alertmanager config: %w", err)
	}

	m, err := definition.Merge(c.AlertmanagerConfig, mcfg, opts)
	if err != nil {
		return MergeResult{}, fmt.Errorf("failed to merge alertmanager config: %w", err)
	}
	return MergeResult(m), nil
}

// GetMergedTemplateDefinitions converts the given PostableUserConfig's TemplateFiles to a slice of Templates.
func (c *PostableUserConfig) GetMergedTemplateDefinitions() []definition.PostableApiTemplate {
	out := definition.TemplatesMapToPostableAPITemplates(c.TemplateFiles, definition.GrafanaTemplateKind)
	if len(c.ExtraConfigs) == 0 || len(c.ExtraConfigs[0].TemplateFiles) == 0 {
		return out
	}
	return append(out, definition.TemplatesMapToPostableAPITemplates(c.ExtraConfigs[0].TemplateFiles, definition.MimirTemplateKind)...)
}

func (c *PostableUserConfig) UnmarshalJSON(b []byte) error {
	type plain PostableUserConfig
	if err := json.Unmarshal(b, (*plain)(c)); err != nil {
		return err
	}

	// validate first
	if err := c.validate(); err != nil {
		return err
	}

	if len(c.ExtraConfigs) > 1 {
		return errors.New("only one extra config is supported")
	}

	type intermediate struct {
		AlertmanagerConfig map[string]interface{} `yaml:"alertmanager_config" json:"alertmanager_config"`
	}

	var tmp intermediate
	if err := json.Unmarshal(b, &tmp); err != nil {
		return err
	}
	// store the map[string]interface{} variant for re-encoding later without redaction
	c.amSimple = tmp.AlertmanagerConfig

	return nil
}

func (c *PostableUserConfig) validate() error {
	// Taken from https://github.com/prometheus/alertmanager/blob/master/config/config.go#L170-L191
	// Check if we have a root route. We cannot check for it in the
	// UnmarshalYAML method because it won't be called if the input is empty
	// (e.g. the config file is empty or only contains whitespace).
	if c.AlertmanagerConfig.Route == nil {
		return fmt.Errorf("no route provided in config")
	}

	// Check if continue in root route.
	if c.AlertmanagerConfig.Route.Continue {
		return fmt.Errorf("cannot have continue in root route")
	}

	return nil
}

// GetGrafanaReceiverMap returns a map that associates UUIDs to grafana receivers
func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafanaReceiver {
	UIDs := make(map[string]*PostableGrafanaReceiver)
	for _, r := range c.AlertmanagerConfig.Receivers {
		for _, gr := range r.GrafanaManagedReceivers {
			UIDs[gr.UID] = gr
		}
	}
	return UIDs
}

// MarshalYAML implements yaml.Marshaller.
func (c *PostableUserConfig) MarshalYAML() (interface{}, error) {
	yml, err := yaml.Marshal(c.amSimple)
	if err != nil {
		return nil, err
	}
	// cortex/loki actually pass the AM config as a string.
	cortexPostableUserConfig := struct {
		TemplateFiles      map[string]string `yaml:"template_files" json:"template_files"`
		AlertmanagerConfig string            `yaml:"alertmanager_config" json:"alertmanager_config"`
	}{
		TemplateFiles:      c.TemplateFiles,
		AlertmanagerConfig: string(yml),
	}
	return cortexPostableUserConfig, nil
}

func (c *PostableUserConfig) UnmarshalYAML(value *yaml.Node) error {
	// cortex/loki actually pass the AM config as a string.
	type cortexPostableUserConfig struct {
		TemplateFiles      map[string]string `yaml:"template_files" json:"template_files"`
		AlertmanagerConfig string            `yaml:"alertmanager_config" json:"alertmanager_config"`
	}

	var tmp cortexPostableUserConfig

	if err := value.Decode(&tmp); err != nil {
		return err
	}

	if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
		return err
	}

	c.TemplateFiles = tmp.TemplateFiles
	return nil
}

// swagger:model
type GettableUserConfig struct {
	TemplateFiles           map[string]string         `yaml:"template_files" json:"template_files"`
	TemplateFileProvenances map[string]Provenance     `yaml:"template_file_provenances,omitempty" json:"template_file_provenances,omitempty"`
	AlertmanagerConfig      GettableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
	ExtraConfigs            []ExtraConfiguration      `yaml:"extra_config,omitempty" json:"extra_config,omitempty"`

	// amSimple stores a map[string]interface of the decoded alertmanager config.
	// This enables circumventing the underlying alertmanager secret type
	// which redacts itself during encoding.
	amSimple map[string]interface{} `yaml:"-" json:"-"`
}

func (c *GettableUserConfig) UnmarshalYAML(value *yaml.Node) error {
	// cortex/loki actually pass the AM config as a string.
	type cortexGettableUserConfig struct {
		TemplateFiles      map[string]string `yaml:"template_files" json:"template_files"`
		AlertmanagerConfig string            `yaml:"alertmanager_config" json:"alertmanager_config"`
	}

	var tmp cortexGettableUserConfig

	if err := value.Decode(&tmp); err != nil {
		return err
	}

	if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
		return err
	}

	if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.amSimple); err != nil {
		return err
	}

	c.TemplateFiles = tmp.TemplateFiles
	return nil
}

func (c *GettableUserConfig) MarshalJSON() ([]byte, error) {
	type plain struct {
		TemplateFiles      map[string]string      `yaml:"template_files" json:"template_files"`
		AlertmanagerConfig map[string]interface{} `yaml:"alertmanager_config" json:"alertmanager_config"`
	}

	tmp := plain{
		TemplateFiles:      c.TemplateFiles,
		AlertmanagerConfig: c.amSimple,
	}

	return json.Marshal(tmp)
}

// GetGrafanaReceiverMap returns a map that associates UUIDs to grafana receivers
func (c *GettableUserConfig) GetGrafanaReceiverMap() map[string]*GettableGrafanaReceiver {
	UIDs := make(map[string]*GettableGrafanaReceiver)
	for _, r := range c.AlertmanagerConfig.Receivers {
		for _, gr := range r.GrafanaManagedReceivers {
			UIDs[gr.UID] = gr
		}
	}
	return UIDs
}

type GettableHistoricUserConfig struct {
	ID                      int64                     `yaml:"id" json:"id"`
	TemplateFiles           map[string]string         `yaml:"template_files" json:"template_files"`
	TemplateFileProvenances map[string]Provenance     `yaml:"template_file_provenances,omitempty" json:"template_file_provenances,omitempty"`
	AlertmanagerConfig      GettableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
	LastApplied             *strfmt.DateTime          `yaml:"last_applied,omitempty" json:"last_applied,omitempty"`
}

// swagger:response GettableHistoricUserConfigs
type GettableHistoricUserConfigs struct {
	// in:body
	Body []GettableHistoricUserConfig
}

type GettableApiAlertingConfig struct {
	Config              `yaml:",inline"`
	MuteTimeProvenances map[string]Provenance `yaml:"muteTimeProvenances,omitempty" json:"muteTimeProvenances,omitempty"`
	// Override with our superset receiver type
	Receivers []*GettableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}

func (c *GettableApiAlertingConfig) GetReceivers() []*GettableApiReceiver {
	return c.Receivers
}

func (c *GettableApiAlertingConfig) GetMuteTimeIntervals() []config.MuteTimeInterval {
	return c.MuteTimeIntervals
}

func (c *GettableApiAlertingConfig) GetTimeIntervals() []config.TimeInterval { return c.TimeIntervals }

func (c *GettableApiAlertingConfig) GetRoute() *Route {
	return c.Route
}

func (c *GettableApiAlertingConfig) UnmarshalJSON(b []byte) error {
	type plain GettableApiAlertingConfig
	if err := json.Unmarshal(b, (*plain)(c)); err != nil {
		return err
	}

	// Since Config implements json.Unmarshaler, we must handle _all_ other fields independently.
	// Otherwise, the json decoder will detect this and only use the embedded type.
	// Additionally, we'll use pointers to slices in order to reference the intended target.
	type overrides struct {
		Receivers *[]*GettableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
	}

	if err := json.Unmarshal(b, &overrides{Receivers: &c.Receivers}); err != nil {
		return err
	}

	return c.validate()
}

func (c *GettableApiAlertingConfig) UnmarshalYAML(value *yaml.Node) error {
	type plain GettableApiAlertingConfig
	if err := value.Decode((*plain)(c)); err != nil {
		return err
	}

	// Since Config implements yaml.Unmarshaler, we must handle _all_ other fields independently.
	// Otherwise, the yaml decoder will detect this and only use the embedded type.
	// Additionally, we'll use pointers to slices in order to reference the intended target.
	type overrides struct {
		Receivers *[]*GettableApiReceiver `yaml:"receivers,omitempty"`
	}

	if err := value.Decode(&overrides{Receivers: &c.Receivers}); err != nil {
		return err
	}

	return c.validate()
}

// validate ensures that the two routing trees use the correct receiver types.
func (c *GettableApiAlertingConfig) validate() error {
	receivers := make(map[string]struct{}, len(c.Receivers))

	// Populate the receivers map with defined receiver names
	for _, receiver := range c.Receivers {
		receivers[receiver.Name] = struct{}{}
	}

	for _, receiver := range AllReceivers(c.Route.AsAMRoute()) {
		_, ok := receivers[receiver]
		if !ok {
			return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
		}
	}

	return nil
}

type GettableGrafanaReceiver struct {
	UID                   string          `json:"uid"`
	Name                  string          `json:"name"`
	Type                  string          `json:"type"`
	DisableResolveMessage bool            `json:"disableResolveMessage"`
	Settings              RawMessage      `json:"settings,omitempty"`
	SecureFields          map[string]bool `json:"secureFields"`
	Provenance            Provenance      `json:"provenance,omitempty"`
}

type GettableApiReceiver struct {
	config.Receiver          `yaml:",inline"`
	GettableGrafanaReceivers `yaml:",inline"`
}

func (r *GettableApiReceiver) UnmarshalJSON(b []byte) error {
	type plain GettableApiReceiver
	return json.Unmarshal(b, (*plain)(r))
}

func (r *GettableApiReceiver) GetName() string {
	return r.Name
}

type GettableGrafanaReceivers struct {
	GrafanaManagedReceivers []*GettableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
}

type EncryptFn func(ctx context.Context, payload []byte) ([]byte, error)
