package cloudmigrationimpl

import (
	"context"
	"fmt"
	"time"

	"github.com/prometheus/alertmanager/config"
	"github.com/prometheus/common/model"

	"github.com/grafana/grafana/pkg/components/simplejson"
	ac "github.com/grafana/grafana/pkg/services/accesscontrol"
	ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api/compat"
	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/grafana/grafana/pkg/services/ngalert/models"
	"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
	"github.com/grafana/grafana/pkg/services/org"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/setting"
)

type muteTimeInterval struct {
	UID string `json:"uid"`

	// There is a lot of custom (de)serialization logic from Alertmanager,
	// and this is the same type used by the underlying API, hence we can use the type as-is.
	config.MuteTimeInterval `json:",inline"`
}

func (s *Service) getAlertMuteTimings(ctx context.Context, signedInUser *user.SignedInUser) ([]muteTimeInterval, error) {
	muteTimings, err := s.ngAlert.Api.MuteTimings.GetMuteTimings(ctx, signedInUser.OrgID)
	if err != nil {
		return nil, fmt.Errorf("fetching ngalert mute timings: %w", err)
	}

	muteTimeIntervals := make([]muteTimeInterval, 0, len(muteTimings))

	for _, muteTiming := range muteTimings {
		muteTimeIntervals = append(muteTimeIntervals, muteTimeInterval{
			UID: muteTiming.UID,
			MuteTimeInterval: config.MuteTimeInterval{
				Name:          muteTiming.Name,
				TimeIntervals: muteTiming.TimeIntervals,
			},
		})
	}

	return muteTimeIntervals, nil
}

type notificationTemplate struct {
	UID      string `json:"uid"`
	Name     string `json:"name"`
	Template string `json:"template"`
}

func (s *Service) getNotificationTemplates(ctx context.Context, signedInUser *user.SignedInUser) ([]notificationTemplate, error) {
	templates, err := s.ngAlert.Api.Templates.GetTemplates(ctx, signedInUser.OrgID)
	if err != nil {
		return nil, fmt.Errorf("fetching ngalert notification templates: %w", err)
	}

	notificationTemplates := make([]notificationTemplate, 0, len(templates))

	for _, template := range templates {
		notificationTemplates = append(notificationTemplates, notificationTemplate{
			UID:      template.UID,
			Name:     template.Name,
			Template: template.Template,
		})
	}

	return notificationTemplates, nil
}

type contactPoint struct {
	Settings              *simplejson.Json `json:"settings"`
	UID                   string           `json:"uid"`
	Name                  string           `json:"name"`
	Type                  string           `json:"type"`
	DisableResolveMessage bool             `json:"disableResolveMessage"`
}

func (s *Service) getContactPoints(ctx context.Context, signedInUser *user.SignedInUser) ([]contactPoint, error) {
	userIsOrgAdmin := signedInUser.HasRole(org.RoleAdmin)
	hasAccess, _ := s.accessControl.Evaluate(ctx, signedInUser, ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets, models.ScopeReceiversAll))
	if !userIsOrgAdmin && !hasAccess {
		msg := "user '%s' is not allowed to read contact point secrets, missing 'alert.notifications.receivers.secrets:read' permission, which can be granted through the 'Admin' or 'Alerting > Full admin access' roles"
		return nil, fmt.Errorf(msg, signedInUser.UserUID)
	}

	query := provisioning.ContactPointQuery{
		OrgID:   signedInUser.GetOrgID(),
		Decrypt: true, // needed to recreate the settings in the target instance.
	}

	embeddedContactPoints, err := s.ngAlert.Api.ContactPointService.GetContactPoints(ctx, query, signedInUser)
	if err != nil {
		return nil, fmt.Errorf("fetching ngalert contact points: %w", err)
	}

	contactPoints := make([]contactPoint, 0, len(embeddedContactPoints))

	for _, embeddedContactPoint := range embeddedContactPoints {
		// This happens in the default contact point, and would otherwise fail to migrate because it has no UID.
		// If that contact point is edited in any way, an UID is generated.
		if embeddedContactPoint.UID == "" {
			continue
		}

		contactPoints = append(contactPoints, contactPoint{
			UID:                   embeddedContactPoint.UID,
			Name:                  embeddedContactPoint.Name,
			Type:                  embeddedContactPoint.Type,
			Settings:              embeddedContactPoint.Settings,
			DisableResolveMessage: embeddedContactPoint.DisableResolveMessage,
		})
	}

	return contactPoints, nil
}

type notificationPolicy struct {
	Name   string
	Routes definitions.Route
}

func (s *Service) getNotificationPolicies(ctx context.Context, signedInUser *user.SignedInUser) (notificationPolicy, error) {
	policyTree, _, err := s.ngAlert.Api.Policies.GetPolicyTree(ctx, signedInUser.GetOrgID())
	if err != nil {
		return notificationPolicy{}, fmt.Errorf("fetching ngalert notification policy tree: %w", err)
	}

	return notificationPolicy{
		Name:   "Notification Policy Tree",
		Routes: policyTree,
	}, nil
}

type alertRule struct {
	Updated              time.Time                                  `json:"updated,omitempty"`
	Annotations          map[string]string                          `json:"annotations,omitempty"`
	Labels               map[string]string                          `json:"labels,omitempty"`
	Record               *definitions.Record                        `json:"record"`
	NotificationSettings *definitions.AlertRuleNotificationSettings `json:"notification_settings"`
	FolderUID            string                                     `json:"folderUID"`
	RuleGroup            string                                     `json:"ruleGroup"`
	NoDataState          string                                     `json:"noDataState"`
	Condition            string                                     `json:"condition"`
	UID                  string                                     `json:"uid"`
	Title                string                                     `json:"title"`
	ExecErrState         string                                     `json:"execErrState"`
	Data                 []definitions.AlertQuery                   `json:"data"`
	For                  model.Duration                             `json:"for"`
	OrgID                int64                                      `json:"orgID"`
	IsPaused             bool                                       `json:"isPaused"`
}

func (s *Service) getAlertRules(ctx context.Context, signedInUser *user.SignedInUser) ([]alertRule, error) {
	alertRules, _, err := s.ngAlert.Api.AlertRules.GetAlertRules(ctx, signedInUser)
	if err != nil {
		return nil, fmt.Errorf("fetching alert rules: %w", err)
	}

	settingAlertRulesPaused := s.cfg.CloudMigration.AlertRulesState == setting.GMSAlertRulesPaused

	provisionedAlertRules := make([]alertRule, 0, len(alertRules))

	for _, rule := range alertRules {
		isPaused := rule.IsPaused
		if settingAlertRulesPaused {
			isPaused = true
		}

		provisionedAlertRules = append(provisionedAlertRules, alertRule{
			UID:                  rule.UID,
			OrgID:                rule.OrgID,
			FolderUID:            rule.NamespaceUID,
			RuleGroup:            rule.RuleGroup,
			Title:                rule.Title,
			For:                  model.Duration(rule.For),
			Condition:            rule.Condition,
			Data:                 ngalertapi.ApiAlertQueriesFromAlertQueries(rule.Data),
			Updated:              rule.Updated,
			NoDataState:          rule.NoDataState.String(),
			ExecErrState:         rule.ExecErrState.String(),
			Annotations:          rule.Annotations,
			Labels:               rule.Labels,
			IsPaused:             isPaused,
			NotificationSettings: ngalertapi.AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
			Record:               ngalertapi.ApiRecordFromModelRecord(rule.Record),
		})
	}

	return provisionedAlertRules, nil
}

type alertRuleGroup struct {
	Title     string      `json:"title"`
	FolderUID string      `json:"folderUid"`
	Interval  int64       `json:"interval"`
	Rules     []alertRule `json:"rules"`
}

func (s *Service) getAlertRuleGroups(ctx context.Context, signedInUser *user.SignedInUser) ([]alertRuleGroup, error) {
	alertRuleGroupsWithFolder, err := s.ngAlert.Api.AlertRules.GetAlertGroupsWithFolderFullpath(ctx, signedInUser, nil)
	if err != nil {
		return nil, fmt.Errorf("fetching alert rule groups with folders: %w", err)
	}

	settingAlertRulesPaused := s.cfg.CloudMigration.AlertRulesState == setting.GMSAlertRulesPaused

	alertRuleGroups := make([]alertRuleGroup, 0, len(alertRuleGroupsWithFolder))

	for _, ruleGroup := range alertRuleGroupsWithFolder {
		provisionedAlertRules := make([]alertRule, 0, len(ruleGroup.Rules))

		for _, rule := range ruleGroup.Rules {
			isPaused := rule.IsPaused
			if settingAlertRulesPaused {
				isPaused = true
			}

			provisionedAlertRules = append(provisionedAlertRules, alertRule{
				UID:                  rule.UID,
				OrgID:                rule.OrgID,
				FolderUID:            rule.NamespaceUID,
				RuleGroup:            rule.RuleGroup,
				Title:                rule.Title,
				For:                  model.Duration(rule.For),
				Condition:            rule.Condition,
				Data:                 ngalertapi.ApiAlertQueriesFromAlertQueries(rule.Data),
				Updated:              rule.Updated,
				NoDataState:          rule.NoDataState.String(),
				ExecErrState:         rule.ExecErrState.String(),
				Annotations:          rule.Annotations,
				Labels:               rule.Labels,
				IsPaused:             isPaused,
				NotificationSettings: ngalertapi.AlertRuleNotificationSettingsFromNotificationSettings(rule.NotificationSettings),
				Record:               ngalertapi.ApiRecordFromModelRecord(rule.Record),
			})
		}

		alertRuleGroups = append(alertRuleGroups, alertRuleGroup{
			Title:     ruleGroup.Title,
			FolderUID: ruleGroup.FolderUID,
			Interval:  ruleGroup.Interval,
			Rules:     provisionedAlertRules,
		})
	}

	return alertRuleGroups, nil
}
