// 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.

// This file provides HTTP request builders for testing API endpoints.
package testhelpers

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
)

// Response wraps an HTTP response with parsed JSON data.
// It supports method chaining for assertions.
//
// Example usage:
//
//	testhelpers.GET(t, api, "/api/v1/query", "query", "up").
//		ValidateOpenAPI().
//		RequireSuccess().
//		RequireEquals("$.data.resultType", "vector").
//		RequireLenAtLeast("$.data.result", 1)
//
//	testhelpers.POST(t, api, "/api/v1/query", "query", "up").
//		ValidateOpenAPI().
//		RequireSuccess().
//		RequireArrayContains("$.data.result", expectedValue)
type Response struct {
	StatusCode     int
	Body           string
	JSON           map[string]any
	t              *testing.T
	request        *http.Request
	requestBody    []byte
	responseHeader http.Header
}

// GET sends a GET request to the API and returns a Response with parsed JSON.
// queryParams should be pairs of key-value strings.
func GET(t *testing.T, api *APIWrapper, path string, queryParams ...string) *Response {
	t.Helper()

	if len(queryParams)%2 != 0 {
		t.Fatal("queryParams must be key-value pairs")
	}

	// Build query string.
	values := url.Values{}
	for i := 0; i < len(queryParams); i += 2 {
		values.Add(queryParams[i], queryParams[i+1])
	}

	fullPath := path
	if len(values) > 0 {
		fullPath = path + "?" + values.Encode()
	}

	req := httptest.NewRequest(http.MethodGet, fullPath, http.NoBody)
	return executeRequest(t, api, req)
}

// POST sends a POST request to the API with the given body and returns a Response with parsed JSON.
// bodyParams should be pairs of key-value strings for form data.
func POST(t *testing.T, api *APIWrapper, path string, bodyParams ...string) *Response {
	t.Helper()

	if len(bodyParams)%2 != 0 {
		t.Fatal("bodyParams must be key-value pairs")
	}

	// Build form data.
	values := url.Values{}
	for i := 0; i < len(bodyParams); i += 2 {
		values.Add(bodyParams[i], bodyParams[i+1])
	}

	req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(values.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	return executeRequest(t, api, req)
}

// executeRequest executes an HTTP request and parses the response as JSON.
func executeRequest(t *testing.T, api *APIWrapper, req *http.Request) *Response {
	t.Helper()

	// Capture the request body for validation.
	var requestBody []byte
	if req.Body != nil {
		var err error
		requestBody, err = io.ReadAll(req.Body)
		if err != nil {
			t.Fatalf("failed to read request body: %v", err)
		}
		// Restore the body for the actual request.
		req.Body = io.NopCloser(strings.NewReader(string(requestBody)))
	}

	recorder := httptest.NewRecorder()
	api.Handler.ServeHTTP(recorder, req)

	result := recorder.Result()
	defer result.Body.Close()

	bodyBytes, err := io.ReadAll(result.Body)
	if err != nil {
		t.Fatalf("failed to read response body: %v", err)
	}

	resp := &Response{
		StatusCode:     result.StatusCode,
		Body:           string(bodyBytes),
		t:              t,
		request:        req,
		requestBody:    requestBody,
		responseHeader: result.Header,
	}

	// Try to parse as JSON.
	if result.Header.Get("Content-Type") == "application/json" || strings.Contains(result.Header.Get("Content-Type"), "application/json") {
		var jsonData map[string]any
		if err := json.Unmarshal(bodyBytes, &jsonData); err != nil {
			// If JSON parsing fails, leave JSON as nil.
			// This allows tests to handle non-JSON responses.
			resp.JSON = nil
		} else {
			resp.JSON = jsonData
		}
	}

	return resp
}
