package alert

import (
	"context"
	"errors"

	opsgenieAlert "github.com/opsgenie/opsgenie-go-sdk-v2/alert"
	"github.com/opsgenie/opsgenie-go-sdk-v2/client"
)

// RegisterAlertsFunc is the function that loads the alerts from the corresponding component.
type RegisterAlertsFunc func(cfg AlertConfig) map[string]Alert

type AlertClient interface {
	CreateAlert(key string, alertCtx AlertContext) (Alert, error)
	Send(ctx context.Context, alert Alert) error
	CreateAndSend(ctx context.Context, key string, alertCtx AlertContext) error
}

type AlertConfig struct {
	Environment string
	ApiKey      string
	Enabled     bool
}

// OpsgenieClient is the alert client.
type OpsgenieClient struct {
	enabled bool
	client  *opsgenieAlert.Client
	alerts  map[string]Alert
}

// NewAlertService creates a new alert service
func NewAlertService(cfg AlertConfig, registerAlertsFunc RegisterAlertsFunc) (*OpsgenieClient, error) {
	// load the alert templates from the corresponding component
	alerts := registerAlertsFunc(cfg)

	// create the opsgenie alert client
	alertClient, err := opsgenieAlert.NewClient(&client.Config{ApiKey: cfg.ApiKey})
	if err != nil {
		return nil, err
	}

	return &OpsgenieClient{
		client:  alertClient,
		alerts:  alerts,
		enabled: cfg.Enabled}, nil
}

// CreateAlert creates an alert by key and alert context.
// The key is the alert name, and with it we can get the alert from the registerd alerts.
// The alert context contains the alert execution data
func (s *OpsgenieClient) CreateAlert(key string, alertCtx AlertContext) (Alert, error) {
	if !s.enabled {
		return Alert{}, errors.New("alert not enabled")
	}
	// check alert exists.
	alert, ok := s.alerts[key]
	if !ok {
		return Alert{}, errors.New("alert not found")
	}

	alert.context = alertCtx
	return alert, nil
}

// Send sends an alert to opsgenie.
func (s *OpsgenieClient) Send(ctx context.Context, alert Alert) error {
	if !s.enabled {
		return errors.New("alert not enabled")
	}

	// check alert exists
	if alert.Message == "" {
		return errors.New("message can not be empty")
	}

	// convert alert to an opsgenie alerte request.
	alertRequest := alert.toOpsgenieRequest()

	// create the request
	_, err := s.client.Create(ctx, &alertRequest)
	if err != nil {
		return err
	}
	return nil
}

// CreateAndSend creates an alert by key and alert context and sends it to opsgenie.
func (s *OpsgenieClient) CreateAndSend(ctx context.Context, key string, alertCtx AlertContext) error {
	alert, err := s.CreateAlert(key, alertCtx)
	if err != nil {
		return err
	}
	return s.Send(ctx, alert)
}
