package legacy_storage

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"maps"

	alertingNotify "github.com/grafana/alerting/notify"
	"github.com/grafana/alerting/receivers/schema"

	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/grafana/grafana/pkg/services/ngalert/models"
)

var NameToUid = models.NameToUid

func UidToName(uid string) (string, error) {
	data, err := base64.RawURLEncoding.DecodeString(uid)
	if err != nil {
		return uid, err
	}
	return string(data), nil
}

func IntegrationToPostableGrafanaReceiver(integration *models.Integration) (*apimodels.PostableGrafanaReceiver, error) {
	postable := &apimodels.PostableGrafanaReceiver{
		UID:                   integration.UID,
		Name:                  integration.Name,
		Type:                  string(integration.Config.Type()),
		DisableResolveMessage: integration.DisableResolveMessage,
		SecureSettings:        maps.Clone(integration.SecureSettings),
	}

	// Alertmanager will fail validation with nil Settings , so ensure we always have at least an empty map.
	settings := integration.Settings
	if settings == nil {
		settings = make(map[string]any)
	}

	jsonBytes, err := json.Marshal(settings)
	if err != nil {
		return nil, err
	}
	postable.Settings = jsonBytes
	return postable, nil
}

func ReceiverToPostableApiReceiver(r *models.Receiver) (*apimodels.PostableApiReceiver, error) {
	integrations := apimodels.PostableGrafanaReceivers{
		GrafanaManagedReceivers: make([]*apimodels.PostableGrafanaReceiver, 0, len(r.Integrations)),
	}
	for _, cfg := range r.Integrations {
		postable, err := IntegrationToPostableGrafanaReceiver(cfg)
		if err != nil {
			return nil, err
		}
		integrations.GrafanaManagedReceivers = append(integrations.GrafanaManagedReceivers, postable)
	}

	return &apimodels.PostableApiReceiver{
		Receiver: alertingNotify.ConfigReceiver{
			Name: r.Name,
		},
		PostableGrafanaReceivers: integrations,
	}, nil
}

func PostableApiReceiverToReceiver(postable *apimodels.PostableApiReceiver, provenance models.Provenance, origin models.ResourceOrigin) (*models.Receiver, error) {
	integrations, err := PostableGrafanaReceiversToIntegrations(postable.GrafanaManagedReceivers)
	if err != nil {
		return nil, err
	}
	if postable.HasMimirIntegrations() {
		mimir, err := PostableMimirReceiverToIntegrations(postable.Receiver)
		if err != nil {
			return nil, err
		}
		integrations = append(integrations, mimir...)
	}
	r := &models.Receiver{
		UID:          NameToUid(postable.GetName()), // TODO replace with stable UID.
		Name:         postable.GetName(),
		Integrations: integrations,
		Provenance:   provenance,
		Origin:       origin,
	}
	r.Version = r.Fingerprint()
	return r, nil
}

// GetReceiverProvenance determines the provenance of a definitions.PostableApiReceiver based on the provenance of its integrations.
func GetReceiverProvenance(storedProvenances map[string]models.Provenance, r *apimodels.PostableApiReceiver, origin models.ResourceOrigin) models.Provenance {
	if origin == models.ResourceOriginImported {
		return models.ProvenanceConvertedPrometheus
	}

	if len(r.GrafanaManagedReceivers) == 0 || len(storedProvenances) == 0 {
		return models.ProvenanceNone
	}

	// Current provisioning works on the integration level, so we need some way to determine the provenance of the
	// entire receiver. All integrations in a receiver should have the same provenance, but we don't want to rely on
	// this assumption in case the first provenance is None and a later one is not. To this end, we return the first
	// non-zero provenance we find.
	for _, contactPoint := range r.GrafanaManagedReceivers {
		if p, exists := storedProvenances[contactPoint.UID]; exists && p != models.ProvenanceNone {
			return p
		}
	}
	return models.ProvenanceNone
}

func PostableGrafanaReceiversToIntegrations(postables []*apimodels.PostableGrafanaReceiver) ([]*models.Integration, error) {
	integrations := make([]*models.Integration, 0, len(postables))
	for _, cfg := range postables {
		integration, err := PostableGrafanaReceiverToIntegration(cfg)
		if err != nil {
			return nil, err
		}
		integrations = append(integrations, integration)
	}

	return integrations, nil
}

func PostableMimirReceiverToIntegrations(r alertingNotify.ConfigReceiver) ([]*models.Integration, error) {
	v0, err := alertingNotify.ConfigReceiverToMimirIntegrations(r)
	if err != nil {
		return nil, fmt.Errorf("failed to convert v0 receiver to integrations: %w", err)
	}
	result := make([]*models.Integration, 0, len(v0))
	for _, config := range v0 {
		s, err := config.ConfigMap()
		if err != nil {
			return nil, fmt.Errorf("failed to get settings of v0 receiver %s (version %s): %w", config.Schema.Type(), config.Schema.Version, err)
		}
		result = append(result, &models.Integration{
			Config:         config.Schema,
			Settings:       s,
			SecureSettings: map[string]string{},
		})
	}
	return result, nil
}

func PostableGrafanaReceiverToIntegration(p *apimodels.PostableGrafanaReceiver) (*models.Integration, error) {
	integrationType, err := alertingNotify.IntegrationTypeFromString(p.Type)
	if err != nil {
		return nil, err
	}
	config, ok := alertingNotify.GetSchemaVersionForIntegration(integrationType, schema.V1)
	if !ok {
		return nil, fmt.Errorf("integration type [%s] does not have schema of version %s", integrationType, schema.V1)
	}
	integration := &models.Integration{
		UID:                   p.UID,
		Name:                  p.Name,
		Config:                config,
		DisableResolveMessage: p.DisableResolveMessage,
		Settings:              make(map[string]any, len(p.Settings)),
		SecureSettings:        make(map[string]string, len(p.SecureSettings)),
	}

	if p.Settings != nil {
		if err := json.Unmarshal(p.Settings, &integration.Settings); err != nil {
			return nil, fmt.Errorf("integration '%s' of receiver '%s' has settings that cannot be parsed as JSON: %w", integration.Config.Type(), p.Name, err)
		}
	}

	for k, v := range p.SecureSettings {
		if v != "" {
			integration.SecureSettings[k] = v
		}
	}

	return integration, nil
}
