package alertmanager

import (
	"net/http"
	"testing"
	"time"

	"github.com/prymitive/karma/internal/config"
)

type testCase struct {
	config     config.AlertmanagerConfig
	shouldFail bool
}

var testCases = []testCase{
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "http://localhost:9093",
			ExternalURI: "http://localhost:9093",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    false,
			Headers:     map[string]string{},
		},
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "proxy",
			URI:         "http://localhost:9094",
			ExternalURI: "http://localhost:9094",
			Timeout:     time.Second * 30,
			Proxy:       true,
			ReadOnly:    false,
			Headers:     map[string]string{},
		},
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "http://localhost:9095",
			ExternalURI: "http://localhost:9095",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    true,
			Headers:     map[string]string{},
		},
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "%gh&%ij",
			ExternalURI: "http://localhost:9095",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    true,
			Headers:     map[string]string{},
		},
		shouldFail: true,
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "http://localhost:9095",
			ExternalURI: "%gh&%ij",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    true,
			Headers:     map[string]string{},
		},
		shouldFail: true,
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "http://localhost:9093",
			ExternalURI: "http://localhost:9093",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    false,
			Headers:     map[string]string{},
			Healthcheck: config.AlertmanagerHealthcheck{
				Filters: map[string][]string{
					"prom1": {"@age>a"},
				},
			},
		},
		shouldFail: true,
	},
	{
		config: config.AlertmanagerConfig{
			Cluster:     "cluster",
			Name:        "name",
			URI:         "http://localhost:9093",
			ExternalURI: "http://localhost:9093",
			Timeout:     time.Second * 30,
			Proxy:       false,
			ReadOnly:    false,
			Headers:     map[string]string{},
			Healthcheck: config.AlertmanagerHealthcheck{
				Filters: map[string][]string{
					"prom1": {" "},
				},
			},
		},
		shouldFail: true,
	},
}

func saveUpstreams() map[string]*Alertmanager {
	saved := make(map[string]*Alertmanager, len(upstreams))
	for k, v := range upstreams {
		saved[k] = v
	}
	return saved
}

func restoreUpstreams(saved map[string]*Alertmanager) {
	upstreams = saved
}

func TestUnregisterAll(t *testing.T) {
	// verifies that UnregisterAll removes all registered instances
	saved := saveUpstreams()
	defer restoreUpstreams(saved)

	am, err := NewAlertmanager("cluster", "unreg-test", "http://localhost")
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	_ = RegisterAlertmanager(am)
	UnregisterAll()
	ams := GetAlertmanagers()
	if len(ams) != 0 {
		t.Errorf("Expected 0 alertmanagers after UnregisterAll, got %d", len(ams))
	}
}

func TestRegisterAlertmanagerDuplicate(t *testing.T) {
	// verifies that registering the same name twice returns an error
	saved := saveUpstreams()
	defer restoreUpstreams(saved)
	UnregisterAll()

	am, err := NewAlertmanager("cluster", "dup-test", "http://localhost")
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	if regErr := RegisterAlertmanager(am); regErr != nil {
		t.Fatalf("First RegisterAlertmanager failed: %s", regErr)
	}

	am2, err := NewAlertmanager("cluster", "dup-test", "http://localhost:9094")
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	if err := RegisterAlertmanager(am2); err == nil {
		t.Error("Second RegisterAlertmanager with same name should have returned an error")
	}
}

func TestGetAlertmanagerByName(t *testing.T) {
	saved := saveUpstreams()
	defer restoreUpstreams(saved)
	UnregisterAll()

	am, err := NewAlertmanager("cluster", "lookup-test", "http://localhost")
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	_ = RegisterAlertmanager(am)

	// verifies that a registered instance is found by name
	found := GetAlertmanagerByName("lookup-test")
	if found == nil {
		t.Error("GetAlertmanagerByName returned nil for registered instance")
	}
	if found != nil && found.Name != "lookup-test" {
		t.Errorf("GetAlertmanagerByName returned wrong instance: %s", found.Name)
	}

	// verifies that a non-existent name returns nil
	notFound := GetAlertmanagerByName("does-not-exist")
	if notFound != nil {
		t.Errorf("GetAlertmanagerByName returned non-nil for unregistered name: %s", notFound.Name)
	}
}

func TestWithHealthchecksVisible(t *testing.T) {
	// verifies that WithHealthchecksVisible sets the healthchecksVisible field
	am, err := NewAlertmanager(
		"cluster", "hcv-test", "http://localhost",
		WithHealthchecksVisible(true),
	)
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	if !am.healthchecksVisible {
		t.Error("Expected healthchecksVisible to be true")
	}
}

func TestIsHealthy(t *testing.T) {
	// verifies that a fresh alertmanager with no errors is healthy
	am, err := NewAlertmanager("cluster", "healthy-test", "http://localhost")
	if err != nil {
		t.Fatalf("NewAlertmanager failed: %s", err)
	}
	if !am.IsHealthy() {
		t.Error("Expected fresh alertmanager to be healthy")
	}

	// verifies that setting lastError makes it unhealthy
	am.lock.Lock()
	am.lastError = "something went wrong"
	am.lock.Unlock()
	if am.IsHealthy() {
		t.Error("Expected alertmanager with lastError to be unhealthy")
	}
}

func TestClusterMemberNames(t *testing.T) {
	saved := saveUpstreams()
	defer restoreUpstreams(saved)
	UnregisterAll()

	// register three alertmanagers: two in same cluster, one different
	am1, _ := NewAlertmanager("prod", "am-1", "http://localhost:9091")
	am2, _ := NewAlertmanager("prod", "am-2", "http://localhost:9092")
	am3, _ := NewAlertmanager("staging", "am-3", "http://localhost:9093")
	_ = RegisterAlertmanager(am1)
	_ = RegisterAlertmanager(am2)
	_ = RegisterAlertmanager(am3)

	// verifies that am1 sees itself and am2 as cluster members, but not am3
	members := am1.ClusterMemberNames()
	if len(members) != 2 {
		t.Errorf("Expected 2 cluster members, got %d: %v", len(members), members)
	}
	if members[0] != "am-1" || members[1] != "am-2" {
		t.Errorf("Expected [am-1 am-2], got %v", members)
	}

	// verifies that am3 only sees itself
	members3 := am3.ClusterMemberNames()
	if len(members3) != 1 {
		t.Errorf("Expected 1 cluster member for staging, got %d: %v", len(members3), members3)
	}
	if members3[0] != "am-3" {
		t.Errorf("Expected [am-3], got %v", members3)
	}
}

func TestOptions(t *testing.T) {
	for _, tc := range testCases {
		var httpTransport http.RoundTripper
		var err error
		if tc.config.TLS.CA != "" || tc.config.TLS.Cert != "" || tc.config.TLS.InsecureSkipVerify {
			httpTransport, err = NewHTTPTransport(tc.config.TLS.CA, tc.config.TLS.Cert, tc.config.TLS.Key, tc.config.TLS.InsecureSkipVerify)
			if err != nil {
				t.Errorf("Failed to create HTTP transport for Alertmanager '%s' with URI '%s': %s", tc.config.Name, tc.config.URI, err)
			}
		}

		am, err := NewAlertmanager(
			tc.config.Cluster,
			tc.config.Name,
			tc.config.URI,
			WithExternalURI(tc.config.ExternalURI),
			WithRequestTimeout(tc.config.Timeout),
			WithProxy(tc.config.Proxy),
			WithReadOnly(tc.config.ReadOnly),
			WithHTTPTransport(httpTransport), // we will pass a nil unless TLS.CA or TLS.Cert is set
			WithHTTPHeaders(tc.config.Headers),
			WithCORSCredentials(tc.config.CORS.Credentials),
			WithHealthchecks(tc.config.Healthcheck.Filters),
		)
		didFail := err != nil
		if didFail != tc.shouldFail {
			t.Errorf("Alertmanager '%s' error mismatch, shouldFail=%v, err=%v", tc.config.Name, tc.shouldFail, err)
		}
		if err != nil {
			t.Logf("Alertmanager '%s' returned an error: %v", tc.config.Name, err)
			continue
		}
		if am.Name != tc.config.Name {
			t.Errorf("AlertmanagerConfig with name '%s' returned Alertmanager with name '%s'", tc.config.Name, am.Name)
		}
		if am.URI != tc.config.URI {
			t.Errorf("AlertmanagerConfig with name '%s' and URI '%s' returned Alertmanager with URI '%s'", tc.config.Name, tc.config.URI, am.URI)
		}
		if am.ProxyRequests != tc.config.Proxy {
			t.Errorf("AlertmanagerConfig with name '%s' and proxy '%v' returned Alertmanager with proxy '%v'", tc.config.Name, tc.config.Proxy, am.ProxyRequests)
		}
		if am.ReadOnly != tc.config.ReadOnly {
			t.Errorf("AlertmanagerConfig with name '%s' and readonly '%v' returned Alertmanager with readonly '%v'", tc.config.Name, tc.config.ReadOnly, am.ReadOnly)
		}
		if am.CORSCredentials != tc.config.CORS.Credentials {
			t.Errorf("AlertmanagerConfig with name '%s' and cors:credentials '%v' returned Alertmanager with cors:credentials '%v'", tc.config.Name, tc.config.CORS.Credentials, am.CORSCredentials)
		}
	}
}
