package util

import (
	"testing"

	"github.com/stretchr/testify/assert"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
)

func TestCalcHashNum(t *testing.T) {
	tests := []struct {
		name string
		want int64
	}{
		{
			name: "d38bf7a2-afe1-5ce3-b590-7681d0c8cd79",
			want: 2439664736,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, CalcHashNum(tt.name))
		})
	}
}

func TestCalcMaxDiskUsagePerUrlFromBufferSize(t *testing.T) {
	const reserve = "1Gi"
	tests := []struct {
		name     string
		pvSize   string
		urlCount int
		expected string
	}{
		{
			name:     "single replica small pv",
			pvSize:   "4Gi",
			urlCount: 1,
			expected: "3GiB",
		},
		{
			name:     "multi replica small pv",
			pvSize:   "4Gi",
			urlCount: 2,
			expected: "1536MiB",
		},
		{
			name:     "single replica large pv",
			pvSize:   "20Gi",
			urlCount: 1,
			expected: "19GiB",
		},
		{
			name:     "multi replica large pv",
			pvSize:   "20Gi",
			urlCount: 2,
			expected: "9728MiB",
		},
		{
			name:     "zero replica count (defaults to 1)",
			pvSize:   "20Gi",
			urlCount: 0,
			expected: "19GiB",
		},
		{
			name:     "negative replica count (defaults to 1)",
			pvSize:   "20Gi",
			urlCount: -999,
			expected: "19GiB",
		},
		{
			name:     "invalid pv size",
			pvSize:   "INVALID_VALUE",
			urlCount: 1,
			expected: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			maxDiskUsagePerUrl, err := CalcVmagentMaxDiskUsagePerURLFromPVSize(tt.pvSize, tt.urlCount, reserve)
			if tt.expected != "" {
				assert.NoError(t, err)
				assert.Equal(t, tt.expected, maxDiskUsagePerUrl)
			} else {
				assert.Error(t, err)
			}
		})
	}
}

func TestShouldUpdateO11yVersion(t *testing.T) {
	tests := []struct {
		name    string
		current string
		desired int
		want    bool
	}{
		{name: "empty-current", current: "", desired: 1, want: true},
		{name: "non-numeric", current: "abc", desired: 1, want: true},
		{name: "equal", current: "2", desired: 2, want: false},
		{name: "smaller", current: "1", desired: 2, want: true},
		{name: "greater", current: "3", desired: 2, want: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			annotations := map[string]string{
				consts.AnnotationKeyO11yVersion: tt.current,
			}
			assert.Equal(t, tt.want, ShouldUpdateO11yVersion(annotations, tt.desired))
		})
	}
}

func TestDomainNamespace(t *testing.T) {
	tests := []struct {
		name     string
		domain   string
		expected string
	}{
		{name: "subdomain with dots", domain: "foo.bar.baz", expected: "foo"},
		{name: "single label", domain: "foo", expected: "foo"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, DomainNamespace(tt.domain))
		})
	}
}

func TestInterfaceEndpointName(t *testing.T) {
	tests := []struct {
		name     string
		meta     metav1.ObjectMeta
		domain   string
		prefix   string
		expected string
	}{
		{
			name: "basic",
			meta: metav1.ObjectMeta{
				Name:      "test-obj",
				Namespace: "default",
			},
			domain:   "foo.bar.com",
			prefix:   "vpce",
			expected: "vpce-foo-default-test-obj-6c283d3a",
		},
		{
			name:     "standard case",
			meta:     metav1.ObjectMeta{Namespace: "ns", Name: "agent"},
			domain:   "srv.o11y.internal",
			prefix:   "tidbcloud-o11y",
			expected: "tidbcloud-o11y-srv-ns-agent-b44d29fa",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, InterfaceEndpointName(tt.meta, tt.domain, tt.prefix))
		})
	}
}

func TestCompareVersions(t *testing.T) {
	tests := []struct {
		name     string
		version1 string
		operator string
		version2 string
		want     bool
		wantErr  bool
	}{
		{
			name:     "equal versions",
			version1: "1.0.0",
			operator: "=",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "greater than",
			version1: "1.1.0",
			operator: ">",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "less than",
			version1: "1.0.0",
			operator: "<",
			version2: "1.1.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "greater or equal - equal",
			version1: "1.0.0",
			operator: ">=",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "greater or equal - greater",
			version1: "1.1.0",
			operator: ">=",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "less or equal - equal",
			version1: "1.0.0",
			operator: "<=",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "less or equal - less",
			version1: "0.9.0",
			operator: "<=",
			version2: "1.0.0",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "v prefix handling",
			version1: "v1.2.3",
			operator: "=",
			version2: "1.2.3",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "metadata stripping",
			version1: "1.2.3-beta.1+build.123",
			operator: "=",
			version2: "1.2.3",
			want:     true,
			wantErr:  false,
		},
		{
			name:     "invalid version",
			version1: "invalid",
			operator: "=",
			version2: "1.0.0",
			want:     false,
			wantErr:  true,
		},
		{
			name:     "invalid constraint",
			version1: "1.0.0",
			operator: "invalid",
			version2: "1.0.0",
			want:     false,
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := CompareVersions(tt.version1, tt.operator, tt.version2)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.want, got)
			}
		})
	}
}

func TestIsStatusConditionTrue(t *testing.T) {
	tests := []struct {
		name          string
		conditions    []appsv1.DeploymentCondition
		conditionType appsv1.DeploymentConditionType
		want          bool
	}{
		{
			name: "condition type not present",
			conditions: []appsv1.DeploymentCondition{
				{
					Type:   appsv1.DeploymentProgressing,
					Status: corev1.ConditionTrue,
				},
			},
			conditionType: appsv1.DeploymentAvailable,
			want:          false,
		},
		{
			name: "condition type present but status is False",
			conditions: []appsv1.DeploymentCondition{
				{
					Type:   appsv1.DeploymentAvailable,
					Status: corev1.ConditionFalse,
				},
			},
			conditionType: appsv1.DeploymentAvailable,
			want:          false,
		},
		{
			name: "condition type present but status is Unknown",
			conditions: []appsv1.DeploymentCondition{
				{
					Type:   appsv1.DeploymentAvailable,
					Status: corev1.ConditionUnknown,
				},
			},
			conditionType: appsv1.DeploymentAvailable,
			want:          false,
		},
		{
			name: "condition type present and status is True",
			conditions: []appsv1.DeploymentCondition{
				{
					Type:   appsv1.DeploymentAvailable,
					Status: corev1.ConditionTrue,
				},
			},
			conditionType: appsv1.DeploymentAvailable,
			want:          true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, IsStatusConditionTrue(tt.conditions, tt.conditionType))
		})
	}
}

func testComponentSpec(t *testing.T, key string, targetFunc func(map[string]string, string, string) string) {
	tests := []struct {
		name       string
		val        string
		keyMissing bool
		defaultVal string
		component  string
		want       string
	}{
		{
			name:       "key missing",
			keyMissing: true,
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "default",
		},
		{
			name:       "single value",
			val:        "single",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "single",
		},
		{
			name:       "single value for vector",
			val:        "single",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "single",
		},
		{
			name:       "slash separated - vmagent",
			val:        "vm/vec",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "vm",
		},
		{
			name:       "slash separated - vector",
			val:        "vm/vec",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "vec",
		},
		{
			name:       "slash separated - empty vmagent part",
			val:        "/vec",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "default",
		},
		{
			name:       "slash separated - empty vector part",
			val:        "vm/",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "default",
		},
		{
			name:       "invalid format - too many parts",
			val:        "a/b/c",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "default",
		},
		{
			name:       "empty value - vmagent",
			val:        "",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "default",
		},
		{
			name:       "empty value - vector",
			val:        "",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "default",
		},
		{
			name:       "slash only - vmagent",
			val:        "/",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "default",
		},
		{
			name:       "slash only - vector",
			val:        "/",
			defaultVal: "default",
			component:  consts.ComponentVector,
			want:       "default",
		},
		{
			name:       "double slash",
			val:        "//",
			defaultVal: "default",
			component:  consts.ComponentVMAgent,
			want:       "default",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			annotations := map[string]string{}
			if !tt.keyMissing {
				annotations[key] = tt.val
			}
			assert.Equal(t, tt.want, targetFunc(annotations, tt.defaultVal, tt.component))
		})
	}
}

func TestGetComponentSpecs(t *testing.T) {
	tests := []struct {
		name string
		key  string
		fn   func(map[string]string, string, string) string
	}{
		{
			name: "DesiredSpec",
			key:  consts.AnnotationKeyO11yDesiredSpec,
			fn:   GetComponentDesiredSpec,
		},
		{
			name: "MinSpec",
			key:  consts.AnnotationKeyO11yMinSpec,
			fn:   GetComponentMinSpec,
		},
		{
			name: "MaxSpec",
			key:  consts.AnnotationKeyO11yMaxSpec,
			fn:   GetComponentMaxSpec,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			testComponentSpec(t, tt.key, tt.fn)
		})
	}
}
