package service

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

// MockMetricsClient is a mock implementation of MetricsClient
type MockMetricsClient struct {
	mock.Mock
}

func (m *MockMetricsClient) GetPodCPUAndMemory(ctx context.Context, namespace, podName string) (float64, uint64, error) {
	args := m.Called(ctx, namespace, podName)
	return args.Get(0).(float64), args.Get(1).(uint64), args.Error(2)
}

func TestGetComponentResourceMetrics_NoPods(t *testing.T) {
	// Test implementation
	// This is a placeholder - full test would need k8s fake client setup
}

func TestGetComponentResourceMetrics_TiKVWorkerCSNGTypeUsesPodLabels(t *testing.T) {
	scheme := runtime.NewScheme()
	require.NoError(t, corev1.AddToScheme(scheme))

	const (
		clusterID   = "2070062522060242944"
		componentID = "fbc48e4a"
	)
	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "db-fbc48e4a-default-tikv-worker-zti4e9",
			Namespace: buildNamespace(clusterID),
			Labels: map[string]string{
				K8sComponentLabelKey: TiKVWorkerLabel,
				K8sInstanceLabelKey:  "db-" + componentID,
			},
		},
	}
	svc := &AgentService{
		dataplaneCli: fake.NewClientBuilder().WithScheme(scheme).WithObjects(pod).Build(),
	}

	metrics, err := svc.GetComponentResourceMetrics(context.Background(), &apiv1.GetComponentResourceMetricsReq{
		ClusterId:     clusterID,
		ComponentType: "default-tikv-worker",
		ComponentId:   componentID,
	})

	require.NoError(t, err)
	assert.Len(t, metrics.Components, 1)
	assert.Equal(t, pod.Name, metrics.Components[0].PodName)
	assert.Equal(t, "default-tikv-worker", metrics.Components[0].ComponentType)
	assert.Equal(t, componentID, metrics.Components[0].ComponentId)
	assert.False(t, metrics.MetricsServerAvailable)
}

func TestBuildPodMetrics(t *testing.T) {
	svc := &AgentService{}

	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-pod",
			Namespace: "tidb-test",
		},
		Spec: corev1.PodSpec{
			NodeName: "test-node",
			Containers: []corev1.Container{
				{
					Name: "tikv",
					Resources: corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("4"),
							corev1.ResourceMemory: resource.MustParse("16Gi"),
						},
						Requests: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("2"),
							corev1.ResourceMemory: resource.MustParse("8Gi"),
						},
					},
				},
			},
		},
		Status: corev1.PodStatus{
			ContainerStatuses: []corev1.ContainerStatus{
				{
					Name:         "tikv",
					RestartCount: 1,
				},
			},
		},
	}

	// Test without metrics client
	metrics, success := svc.buildPodMetrics(context.Background(), pod, "tidb-test", "tikv", "default")
	assert.False(t, success)

	assert.Equal(t, "tikv", metrics.ComponentType)
	assert.Equal(t, "default", metrics.ComponentId)
	assert.Equal(t, "test-pod", metrics.PodName)
	assert.Equal(t, "test-node", metrics.NodeName)
	assert.InDelta(t, float64(4), metrics.CpuCoresTotal, 0.001)
	assert.Equal(t, uint64(16*1024*1024*1024), metrics.MemoryBytesTotal)
	// Without metrics client, usage should be 0
	assert.InDelta(t, float64(0), metrics.CpuCoresUsed, 0.001)
	assert.Equal(t, uint64(0), metrics.MemoryBytesUsed)
}

func TestBuildPodMetrics_WithMetricsClient(t *testing.T) {
	mockMetrics := &MockMetricsClient{}
	svc := &AgentService{
		metricsCli: mockMetrics,
	}

	pod := &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-pod",
			Namespace: "tidb-test",
		},
		Spec: corev1.PodSpec{
			NodeName: "test-node",
			Containers: []corev1.Container{
				{
					Name: "tikv",
					Resources: corev1.ResourceRequirements{
						Limits: corev1.ResourceList{
							corev1.ResourceCPU:    resource.MustParse("4"),
							corev1.ResourceMemory: resource.MustParse("16Gi"),
						},
					},
				},
			},
		},
	}

	// Mock metrics client to return usage values
	mockMetrics.On("GetPodCPUAndMemory", mock.Anything, "tidb-test", "test-pod").
		Return(float64(2.5), uint64(8*1024*1024*1024), nil)

	metrics, success := svc.buildPodMetrics(context.Background(), pod, "tidb-test", "tikv", "default")
	assert.True(t, success)

	assert.Equal(t, "tikv", metrics.ComponentType)
	assert.Equal(t, "test-pod", metrics.PodName)
	assert.InDelta(t, float64(4), metrics.CpuCoresTotal, 0.001)
	assert.Equal(t, uint64(16*1024*1024*1024), metrics.MemoryBytesTotal)
	// With metrics client, usage should be populated
	assert.InDelta(t, float64(2.5), metrics.CpuCoresUsed, 0.001)
	assert.Equal(t, uint64(8*1024*1024*1024), metrics.MemoryBytesUsed)

	mockMetrics.AssertExpectations(t)
}

func TestGetPodCPULimit(t *testing.T) {
	tests := []struct {
		name     string
		pod      *corev1.Pod
		expected float64
	}{
		{
			name: "with limits",
			pod: &corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceCPU: resource.MustParse("2"),
								},
							},
						},
						{
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceCPU: resource.MustParse("1.5"),
								},
							},
						},
					},
				},
			},
			expected: 3.5,
		},
		{
			name: "with requests fallback",
			pod: &corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Resources: corev1.ResourceRequirements{
								Limits: nil, // Nil limits
								Requests: corev1.ResourceList{
									corev1.ResourceCPU: resource.MustParse("500m"),
								},
							},
						},
					},
				},
			},
			expected: 0.5,
		},
		{
			name: "no resources",
			pod: &corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{},
					},
				},
			},
			expected: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getPodCPULimit(tt.pod)
			assert.InDelta(t, tt.expected, result, 0.001)
		})
	}
}

func TestGetPodMemoryLimit(t *testing.T) {
	tests := []struct {
		name     string
		pod      *corev1.Pod
		expected uint64
	}{
		{
			name: "with limits",
			pod: &corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceMemory: resource.MustParse("8Gi"),
								},
							},
						},
						{
							Resources: corev1.ResourceRequirements{
								Limits: corev1.ResourceList{
									corev1.ResourceMemory: resource.MustParse("4Gi"),
								},
							},
						},
					},
				},
			},
			expected: 12 * 1024 * 1024 * 1024, // 12 Gi
		},
		{
			name: "with requests fallback",
			pod: &corev1.Pod{
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Resources: corev1.ResourceRequirements{
								Limits: nil, // Nil limits
								Requests: corev1.ResourceList{
									corev1.ResourceMemory: resource.MustParse("1Gi"),
								},
							},
						},
					},
				},
			},
			expected: 1 * 1024 * 1024 * 1024, // 1 Gi
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := getPodMemoryLimit(tt.pod)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestSumComponentMetrics(t *testing.T) {
	components := []*apiv1.ComponentResourceMetrics{
		{
			CpuCoresTotal:    4,
			CpuCoresUsed:     2,
			MemoryBytesTotal: 16 * 1024 * 1024 * 1024,
			MemoryBytesUsed:  8 * 1024 * 1024 * 1024,
		},
		{
			CpuCoresTotal:    4,
			CpuCoresUsed:     3,
			MemoryBytesTotal: 16 * 1024 * 1024 * 1024,
			MemoryBytesUsed:  12 * 1024 * 1024 * 1024,
		},
		{
			CpuCoresTotal:    2,
			CpuCoresUsed:     1,
			MemoryBytesTotal: 8 * 1024 * 1024 * 1024,
			MemoryBytesUsed:  4 * 1024 * 1024 * 1024,
		},
	}

	cpuTotal, cpuUsed, memTotal, memUsed := SumComponentMetrics(components)

	assert.InDelta(t, float64(10), cpuTotal, 0.001)
	assert.InDelta(t, float64(6), cpuUsed, 0.001)
	assert.Equal(t, uint64(40*1024*1024*1024), memTotal)
	assert.Equal(t, uint64(24*1024*1024*1024), memUsed)
}

func TestGetComponentTypeLabel(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		// Standard components
		{name: "tikv", input: "tikv", expected: "tikv"},
		{name: "tidb", input: "tidb", expected: "tidb"},
		{name: "pd", input: "pd", expected: "pd"},
		{name: "tiproxy", input: "tiproxy", expected: "tiproxy"},
		{name: "resource-manager", input: "resource-manager", expected: "resource-manager"},

		// Special TiFlash types
		{name: "tiflash-compute", input: "TIFLASH_COMPUTE", expected: "compute-tiflash"},
		{name: "compute-tiflash", input: "compute-tiflash", expected: "compute-tiflash"},
		{name: "tiflash-write", input: "tiflash_write", expected: "write-tiflash"},
		{name: "write-tiflash", input: "write-tiflash", expected: "write-tiflash"},

		// Worker components
		{name: "tidb-worker", input: "TIDB_WORKER", expected: "worker-tidb"},
		{name: "worker-tidb", input: "worker-tidb", expected: "worker-tidb"},
		{name: "default-tikv-worker", input: "default-tikv-worker", expected: "tikv-worker"},
		{name: "tikv-worker", input: "tikv-worker", expected: "tikv-worker"},
		{name: "coprocessor-tikv-worker", input: "coprocessor-tikv-worker", expected: "coprocessor-worker"},
		{name: "coprocessor-worker", input: "COPROCESSOR_WORKER", expected: "coprocessor-worker"},
		{name: "schema-manager-tikv-worker", input: "schema-manager-tikv-worker", expected: "schema-manager"},

		// PD extensions
		{name: "tso", input: "PD_TSO", expected: "tso"},
		{name: "scheduling", input: "PD_SCHEDULING", expected: "scheduling"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := GetComponentTypeLabel(tt.input)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestGetComponentInstanceLabel(t *testing.T) {
	tests := []struct {
		compType string
		compId   string
		expected string
	}{
		// Standard components
		{compType: "tikv", compId: "default", expected: "db-default"},
		{compType: "tidb", compId: "default", expected: "db-default"},
		{compType: "pd", compId: "default", expected: "db-default"},
		{compType: "tiproxy", compId: "default", expected: "db-default"},
		{compType: "tso", compId: "default", expected: "db-default"},
		{compType: "scheduling", compId: "default", expected: "db-default"},

		// TiFlash split types
		{compType: "tiflash-compute", compId: "default", expected: "db-default-compute"},
		{compType: "compute-tiflash", compId: "default", expected: "db-default-compute"},
		{compType: "tiflash-write", compId: "default", expected: "db-default-write"},
		{compType: "write-tiflash", compId: "default", expected: "db-default-write"},

		// Worker components
		{compType: "tidb-worker", compId: "worker1", expected: "db-worker1-worker"},
		{compType: "worker-tidb", compId: "worker1", expected: "db-worker1-worker"},
		{compType: "default-tikv-worker", compId: "worker1", expected: "db-worker1"},
		{compType: "tikv-worker", compId: "worker1", expected: "db-worker1"},
		{compType: "coprocessor-tikv-worker", compId: "copr1", expected: "db-copr1"},
		{compType: "coprocessor-worker", compId: "copr1", expected: "db-copr1"},
		{compType: "schema-manager-tikv-worker", compId: "schema1", expected: "db-schema1"},
	}

	for _, tt := range tests {
		t.Run(fmt.Sprintf("%s-%s", tt.compType, tt.compId), func(t *testing.T) {
			result := GetComponentInstanceLabel(tt.compType, tt.compId)
			assert.Equal(t, tt.expected, result)
		})
	}
}
