// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"

	remoteapi "github.com/prometheus/client_golang/exp/api/remote"
	"github.com/stretchr/testify/require"
)

// setupTestServer creates a test server with a mock storage for remote write testing.
func setupTestServer(t *testing.T, supportedTypes remoteapi.MessageTypes) (*httptest.Server, *mockStorage, *url.URL) {
	t.Helper()

	store := &mockStorage{}
	handler := remoteapi.NewWriteHandler(store, supportedTypes)
	server := httptest.NewServer(handler)
	t.Cleanup(server.Close)

	serverURL, err := url.Parse(server.URL)
	require.NoError(t, err)

	return server, store, serverURL
}

// createMetricsFile creates a temporary file with the given metrics data.
func createMetricsFile(t *testing.T, metricsData string) string {
	t.Helper()

	tmpFile := t.TempDir() + "/metrics.txt"
	err := os.WriteFile(tmpFile, []byte(metricsData), 0o644)
	require.NoError(t, err)

	return tmpFile
}

func TestPushMetrics(t *testing.T) {
	tests := []struct {
		name        string
		metricsData string
		protoMsg    string
		serverTypes remoteapi.MessageTypes
	}{
		{
			name: "successful push with gauge metrics (V1)",
			metricsData: `# HELP test_metric A test metric
# TYPE test_metric gauge
test_metric{label="value1"} 42.0
test_metric{label="value2"} 43.0
`,
			protoMsg:    "prometheus.WriteRequest",
			serverTypes: remoteapi.MessageTypes{remoteapi.WriteV1MessageType},
		},
		{
			name: "successful push with counter metrics (V1)",
			metricsData: `# HELP test_counter A test counter
# TYPE test_counter counter
test_counter 100
`,
			protoMsg:    "prometheus.WriteRequest",
			serverTypes: remoteapi.MessageTypes{remoteapi.WriteV1MessageType},
		},
		{
			name: "successful push with gauge metrics (V2)",
			metricsData: `# HELP test_metric A test metric
# TYPE test_metric gauge
test_metric{label="value1"} 42.0
test_metric{label="value2"} 43.0
`,
			protoMsg:    "io.prometheus.write.v2.Request",
			serverTypes: remoteapi.MessageTypes{remoteapi.WriteV2MessageType},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			_, store, serverURL := setupTestServer(t, tc.serverTypes)
			tmpFile := createMetricsFile(t, tc.metricsData)

			status := PushMetrics(
				serverURL,
				http.DefaultTransport,
				map[string]string{},
				30*time.Second,
				tc.protoMsg,
				map[string]string{"job": "test"},
				tmpFile,
			)

			require.Equal(t, successExitCode, status)
			require.True(t, store.called, "Handler should have been called")
			require.NoError(t, store.lastErr, "Handler should not have returned an error")
			require.NotEmpty(t, store.receivedData, "Request should contain data (compression and decompression successful)")
			require.Contains(t, store.receivedContentType, "application/x-protobuf", "Content-Type should be protobuf")
		})
	}
}

// mockStorage is a simple mock for testing the remote write handler.
type mockStorage struct {
	called              bool
	lastErr             error
	receivedData        []byte
	receivedContentType string
}

func (m *mockStorage) Store(req *http.Request, _ remoteapi.WriteMessageType) (*remoteapi.WriteResponse, error) {
	m.called = true

	// Capture content-type header.
	m.receivedContentType = req.Header.Get("Content-Type")

	if req.Body != nil {
		data, err := io.ReadAll(req.Body)
		if err == nil {
			m.receivedData = data
		}
	}

	if m.lastErr != nil {
		return nil, m.lastErr
	}
	resp := remoteapi.NewWriteResponse()
	resp.SetStatusCode(http.StatusNoContent)
	return resp, nil
}

func TestPushMetricsInvalidProtoMsg(t *testing.T) {
	_, store, serverURL := setupTestServer(t, remoteapi.MessageTypes{remoteapi.WriteV1MessageType})
	tmpFile := createMetricsFile(t, "test_metric 1\n")

	status := PushMetrics(
		serverURL,
		http.DefaultTransport,
		map[string]string{},
		30*time.Second,
		"blablalba", // Invalid protoMsg.
		map[string]string{"job": "test"},
		tmpFile,
	)

	require.Equal(t, failureExitCode, status)
	require.False(t, store.called, "Handler should not have been called for invalid protoMsg")
}
