package notifier

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/prompb"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/promrelabel"
)

func TestAlertManager_Addr(t *testing.T) {
	const addr = "http://localhost"
	am, err := NewAlertManager(addr, nil, promauth.HTTPClientConfig{}, nil, 0)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	if am.Addr() != addr {
		t.Fatalf("expected to have %q; got %q", addr, am.Addr())
	}
}

func TestAlertManager_Send(t *testing.T) {
	const baUser, baPass = "foo", "bar"
	const headerKey, headerValue = "TenantID", "foo"
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(_ http.ResponseWriter, _ *http.Request) {
		t.Fatalf("should not be called")
	})
	c := -1
	mux.HandleFunc(alertManagerPath, func(w http.ResponseWriter, r *http.Request) {
		user, pass, ok := r.BasicAuth()
		if !ok {
			t.Fatalf("unauthorized request")
		}
		if user != baUser || pass != baPass {
			t.Fatalf("wrong creds %q:%q; expected %q:%q", user, pass, baUser, baPass)
		}
		c++
		if r.Method != http.MethodPost {
			t.Fatalf("expected POST method got %s", r.Method)
		}
		switch c {
		case 0:
			conn, _, _ := w.(http.Hijacker).Hijack()
			_ = conn.Close()
		case 1:
			if r.Header.Get(headerKey) != headerValue {
				t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, headerValue, r.Header.Get(headerKey))
			}
			w.WriteHeader(500)
		case 2:
			var a []struct {
				Labels       map[string]string `json:"labels"`
				StartsAt     time.Time         `json:"startsAt"`
				EndAt        time.Time         `json:"endsAt"`
				Annotations  map[string]string `json:"annotations"`
				GeneratorURL string            `json:"generatorURL"`
			}
			if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
				t.Fatalf("can not unmarshal data into alert %s", err)
			}
			if len(a) != 1 {
				t.Fatalf("expected 1 alert in array got %d", len(a))
			}
			if a[0].GeneratorURL != "0/0" {
				t.Fatalf("expected 0/0 as generatorURL got %s", a[0].GeneratorURL)
			}
			if a[0].StartsAt.IsZero() {
				t.Fatalf("expected non-zero start time")
			}
			if a[0].EndAt.IsZero() {
				t.Fatalf("expected non-zero end time")
			}
			if len(a[0].Labels) != 1 {
				t.Fatalf("expected 1 labels got %d", len(a[0].Labels))
			}
			if len(a[0].Annotations) != 2 {
				t.Fatalf("expected 2 annotations got %d", len(a[0].Annotations))
			}
			if r.Header.Get(headerKey) != "bar" {
				t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, headerValue, r.Header.Get(headerKey))
			}
		case 3:
			var a []struct {
				Labels map[string]string `json:"labels"`
			}
			if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
				t.Fatalf("can not unmarshal data into alert %s", err)
			}
			if len(a) != 1 {
				t.Fatalf("expected 1 alert in array got %d", len(a))
			}
			if len(a[0].Labels) != 3 {
				t.Fatalf("expected 3 labels got %d", len(a[0].Labels))
			}
			if a[0].Labels["env"] != "prod" {
				t.Fatalf("expected env label to be prod during relabeling, got %s", a[0].Labels["env"])
			}
			if r.Header.Get(headerKey) != "bar" {
				t.Fatalf("expected header %q to be set to %q; got %q instead", headerKey, "bar", r.Header.Get(headerKey))
			}
		case 4:
			var a []struct {
				Labels map[string]string `json:"labels"`
			}
			if err := json.NewDecoder(r.Body).Decode(&a); err != nil {
				t.Fatalf("can not unmarshal data into alert %s", err)
			}
			if len(a) != 1 {
				t.Fatalf("expected 1 alert in array got %d", len(a))
			}
		}
	})
	srv := httptest.NewServer(mux)
	defer srv.Close()

	aCfg := promauth.HTTPClientConfig{
		BasicAuth: &promauth.BasicAuthConfig{
			Username: baUser,
			Password: promauth.NewSecret(baPass),
		},
		Headers: []string{fmt.Sprintf("%s:%s", headerKey, headerValue)},
	}
	parsedConfigs, err := promrelabel.ParseRelabelConfigsData([]byte(`
- action: drop
  if: '{tenant="0"}'
  regex: ".*"
- target_label: "env"
  replacement: "prod"
  if: '{tenant="1"}'
`))
	if err != nil {
		t.Fatalf("unexpected error when parse relabeling config: %s", err)
	}
	am, err := NewAlertManager(srv.URL+alertManagerPath, func(alert Alert) string {
		return strconv.FormatUint(alert.GroupID, 10) + "/" + strconv.FormatUint(alert.ID, 10)
	}, aCfg, parsedConfigs, 0)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	if err := am.Send(context.Background(), []Alert{{Labels: map[string]string{"a": "b"}}}, [][]prompb.Label{{{Name: "a", Value: "b"}}}, nil); err == nil {
		t.Fatalf("expected connection error got nil")
	}

	if err := am.Send(context.Background(), []Alert{{Labels: map[string]string{"a": "b"}}}, [][]prompb.Label{{{Name: "a", Value: "b"}}}, nil); err == nil {
		t.Fatalf("expected wrong http code error got nil")
	}

	if err := am.Send(context.Background(), []Alert{{
		GroupID:     0,
		Name:        "alert0",
		Start:       time.Now().UTC(),
		End:         time.Now().UTC(),
		Labels:      map[string]string{"alertname": "alert0"},
		Annotations: map[string]string{"a": "b", "c": "d"},
	}}, [][]prompb.Label{{{Name: "alertname", Value: "alert0"}}}, map[string]string{headerKey: "bar"}); err != nil {
		t.Fatalf("unexpected error %s", err)
	}

	if err := am.Send(context.Background(), []Alert{
		// drop tenant0 alert message during relabeling
		{
			Name:   "alert1",
			Labels: map[string]string{"rule": "test", "tenant": "0"},
		},
		{
			Name:   "alert2",
			Labels: map[string]string{"rule": "test", "tenant": "1"},
		},
	}, [][]prompb.Label{{{Name: "rule", Value: "test"}, {Name: "tenant", Value: "0"}}, {{Name: "rule", Value: "test"}, {Name: "tenant", Value: "1"}}}, map[string]string{headerKey: "bar"}); err != nil {
		t.Fatalf("unexpected error %s", err)
	}

	if err := am.Send(context.Background(), []Alert{
		{
			Name:   "alert1",
			Labels: map[string]string{"rule": "test"},
		},
		{
			Name:   "alert2",
			Labels: map[string]string{},
		},
	}, [][]prompb.Label{{{Name: "rule", Value: "test"}}, {{}}}, map[string]string{}); err != nil {
		t.Fatalf("unexpected error %s", err)
	}

	if c != 4 {
		t.Fatalf("expected 4 calls(count from zero) to server got %d", c)
	}
}
