// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package metrics

import (
	"strings"
	"testing"

	"github.com/cilium/hive/hivetest"
	"github.com/prometheus/client_golang/prometheus/testutil"
	"github.com/stretchr/testify/require"

	"github.com/cilium/cilium/api/v1/client/daemon"
	"github.com/cilium/cilium/api/v1/health/client/connectivity"
	healthModels "github.com/cilium/cilium/api/v1/health/models"
	"github.com/cilium/cilium/api/v1/models"
)

var sampleHealthResponse = &daemon.GetHealthzOK{
	Payload: &models.StatusResponse{
		Controllers: models.ControllerStatuses{
			{},
			{
				Status: &models.ControllerStatusStatus{
					ConsecutiveFailureCount: 2,
					FailureCount:            0,
				},
			},
		},
		Ipam: &models.IPAMStatus{
			IPV4: []string{"10.11.0.82", "10.11.0.249", "10.11.0.46"},
			IPV6: []string{"fd00::9d15", "fd00::f61a", "fd00::7712"},
		},
	},
}

var sampleSingleClusterConnectivityResponse = &connectivity.GetStatusOK{
	Payload: &healthModels.HealthStatusResponse{
		Local: &healthModels.SelfStatus{
			Name: "kind-worker",
		},
		Nodes: []*healthModels.NodeStatus{
			{
				HealthEndpoint: &healthModels.EndpointStatus{
					PrimaryAddress: &healthModels.PathStatus{
						HTTP: &healthModels.ConnectivityStatus{
							Latency: 212100,
						},
						Icmp: &healthModels.ConnectivityStatus{
							Latency: 672600,
						},
						IP: "10.244.3.219",
					},
					SecondaryAddresses: []*healthModels.PathStatus{
						{
							HTTP: &healthModels.ConnectivityStatus{
								Latency: 212101,
							},
							Icmp: &healthModels.ConnectivityStatus{
								Latency: 672601,
							},
							IP: "10.244.3.220",
						},
						{
							HTTP: &healthModels.ConnectivityStatus{
								Latency: 212102,
							},
							Icmp: &healthModels.ConnectivityStatus{
								Latency: 672602,
							},
							IP: "10.244.3.221",
						},
					},
				},
				Host: &healthModels.HostStatus{
					PrimaryAddress: &healthModels.PathStatus{
						HTTP: &healthModels.ConnectivityStatus{
							Latency: 165362,
						},
						Icmp: &healthModels.ConnectivityStatus{
							Latency: 704179,
						},
						IP: "172.18.0.3",
					},
					SecondaryAddresses: nil,
				},
				Name: "kind-worker",
			},
		},
	},
}

const expectedStatusMetric = `
# HELP cilium_controllers_failing Number of failing controllers
# TYPE cilium_controllers_failing gauge
cilium_controllers_failing 1
# HELP cilium_ip_addresses Number of allocated IP addresses
# TYPE cilium_ip_addresses gauge
cilium_ip_addresses{family="ipv4"} 3
cilium_ip_addresses{family="ipv6"} 3
# HELP cilium_unreachable_health_endpoints Number of health endpoints that cannot be reached
# TYPE cilium_unreachable_health_endpoints gauge
cilium_unreachable_health_endpoints 0
# HELP cilium_unreachable_nodes Number of nodes that cannot be reached
# TYPE cilium_unreachable_nodes gauge
cilium_unreachable_nodes 0
`

type fakeDaemonClient struct {
	response *daemon.GetHealthzOK
}

type fakeConnectivityClient struct {
	response *connectivity.GetStatusOK
}

func (f *fakeConnectivityClient) GetStatus(params *connectivity.GetStatusParams, opts ...connectivity.ClientOption) (*connectivity.GetStatusOK, error) {
	return f.response, nil
}

func (f *fakeDaemonClient) GetHealthz(params *daemon.GetHealthzParams, opts ...daemon.ClientOption) (*daemon.GetHealthzOK, error) {
	return f.response, nil
}

func Test_statusCollector_Collect(t *testing.T) {
	logger := hivetest.Logger(t)
	tests := []struct {
		name                 string
		healthResponse       *daemon.GetHealthzOK
		connectivityResponse *connectivity.GetStatusOK
		expectedMetric       string
		expectedCount        int
	}{
		{
			name:                 "check status metrics",
			healthResponse:       sampleHealthResponse,
			connectivityResponse: sampleSingleClusterConnectivityResponse,
			expectedCount:        5,
			expectedMetric:       expectedStatusMetric,
		},
		{
			name:           "check status metrics without health checking",
			healthResponse: sampleHealthResponse,
			expectedCount:  3, // Only non-health metrics: controllers_failing, ip_addresses (IPv4 + IPv6)
			expectedMetric: `# HELP cilium_controllers_failing Number of failing controllers
# TYPE cilium_controllers_failing gauge
cilium_controllers_failing 1
# HELP cilium_ip_addresses Number of allocated IP addresses
# TYPE cilium_ip_addresses gauge
cilium_ip_addresses{family="ipv4"} 3
cilium_ip_addresses{family="ipv6"} 3
`,
		},
	}

	for _, tt := range tests {
		t.Log("Test :", tt.name)
		var connectivityClient connectivityStatusGetter
		if tt.connectivityResponse != nil {
			connectivityClient = &fakeConnectivityClient{
				response: tt.connectivityResponse,
			}
		}
		collector := newStatusCollectorWithClients(logger, &fakeDaemonClient{
			response: tt.healthResponse,
		}, connectivityClient)

		// perform static checks such as prometheus naming convention, number of labels matching, etc
		lintProblems, err := testutil.CollectAndLint(collector)
		require.NoError(t, err)
		require.Empty(t, lintProblems)

		// check the number of metrics
		count := testutil.CollectAndCount(collector)
		require.Equal(t, tt.expectedCount, count)

		// compare the metric output
		err = testutil.CollectAndCompare(collector, strings.NewReader(tt.expectedMetric))
		require.NoError(t, err)
	}

}
