package notifier

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/app/vmalert/utils"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
)

// AlertManager represents integration provider with Prometheus alert manager
// https://github.com/prometheus/alertmanager
type AlertManager struct {
	addr    string
	argFunc AlertURLGenerator
	client  *http.Client
	timeout time.Duration

	authCfg *promauth.Config

	metrics *metrics
}

type metrics struct {
	alertsSent       *utils.Counter
	alertsSendErrors *utils.Counter
}

func newMetrics(addr string) *metrics {
	return &metrics{
		alertsSent:       utils.GetOrCreateCounter(fmt.Sprintf("vmalert_alerts_sent_total{addr=%q}", addr)),
		alertsSendErrors: utils.GetOrCreateCounter(fmt.Sprintf("vmalert_alerts_send_errors_total{addr=%q}", addr)),
	}
}

// Close is a destructor method for AlertManager
func (am *AlertManager) Close() {
	am.metrics.alertsSent.Unregister()
	am.metrics.alertsSendErrors.Unregister()
}

// Addr returns address where alerts are sent.
func (am AlertManager) Addr() string { return am.addr }

// Send an alert or resolve message
func (am *AlertManager) Send(ctx context.Context, alerts []Alert) error {
	am.metrics.alertsSent.Add(len(alerts))
	err := am.send(ctx, alerts)
	if err != nil {
		am.metrics.alertsSendErrors.Add(len(alerts))
	}
	return err
}

func (am *AlertManager) send(ctx context.Context, alerts []Alert) error {
	b := &bytes.Buffer{}
	writeamRequest(b, alerts, am.argFunc)

	req, err := http.NewRequest("POST", am.addr, b)
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	if am.timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, am.timeout)
		defer cancel()
	}

	req = req.WithContext(ctx)

	if am.authCfg != nil {
		if auth := am.authCfg.GetAuthHeader(); auth != "" {
			req.Header.Set("Authorization", auth)
		}
	}
	resp, err := am.client.Do(req)
	if err != nil {
		return err
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return fmt.Errorf("failed to read response from %q: %w", am.addr, err)
		}
		return fmt.Errorf("invalid SC %d from %q; response body: %s", resp.StatusCode, am.addr, string(body))
	}
	return nil
}

// AlertURLGenerator returns URL to single alert by given name
type AlertURLGenerator func(Alert) string

const alertManagerPath = "/api/v2/alerts"

// NewAlertManager is a constructor for AlertManager
func NewAlertManager(alertManagerURL string, fn AlertURLGenerator, authCfg promauth.HTTPClientConfig, timeout time.Duration) (*AlertManager, error) {
	tls := &promauth.TLSConfig{}
	if authCfg.TLSConfig != nil {
		tls = authCfg.TLSConfig
	}
	tr, err := utils.Transport(alertManagerURL, tls.CertFile, tls.KeyFile, tls.CAFile, tls.ServerName, tls.InsecureSkipVerify)
	if err != nil {
		return nil, fmt.Errorf("failed to create transport: %w", err)
	}

	ba := &promauth.BasicAuthConfig{}
	if authCfg.BasicAuth != nil {
		ba = authCfg.BasicAuth
	}
	aCfg, err := utils.AuthConfig(ba.Username, ba.Password.String(), ba.PasswordFile, authCfg.BearerToken.String(), authCfg.BearerTokenFile)
	if err != nil {
		return nil, fmt.Errorf("failed to configure auth: %w", err)
	}

	return &AlertManager{
		addr:    alertManagerURL,
		argFunc: fn,
		authCfg: aCfg,
		client:  &http.Client{Transport: tr},
		timeout: timeout,
		metrics: newMetrics(alertManagerURL),
	}, nil
}
