// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package tests

import (
	"bufio"
	"context"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
)

// cleanlyCloseBody drains and closes an HTTP response body to prevent
// HTTP/2 GOAWAY errors caused by closing bodies with unread data.
func cleanlyCloseBody(body io.ReadCloser) error {
	if body == nil {
		return nil
	}
	_, _ = io.Copy(io.Discard, body)
	return body.Close()
}

// "metric name" -> "metric value"
type SimpleNodeMetrics map[string]float64

// URI -> "metric name" -> "metric value"
type SimpleNodesMetrics map[string]SimpleNodeMetrics

// GetSimpleNodeMetrics retrieves the specified metrics the provided node URI.
func GetSimpleNodeMetrics(nodeURI string, metricNames ...string) (SimpleNodeMetrics, error) {
	uri := nodeURI + "/ext/metrics"
	return GetMetricsValue(uri, metricNames...)
}

// GetSimpleNodesMetrics retrieves the specified metrics for the provided node URIs.
func GetSimpleNodesMetrics(nodeURIs []string, metricNames ...string) (SimpleNodesMetrics, error) {
	metrics := make(SimpleNodesMetrics, len(nodeURIs))
	for _, u := range nodeURIs {
		var err error
		metrics[u], err = GetSimpleNodeMetrics(u, metricNames...)
		if err != nil {
			return nil, fmt.Errorf("failed to retrieve metrics for %s: %w", u, err)
		}
	}
	return metrics, nil
}

func GetMetricsValue(url string, metrics ...string) (SimpleNodeMetrics, error) {
	lines, err := getHTTPLines(url)
	if err != nil {
		return nil, err
	}
	mm := make(map[string]float64, len(metrics))
	for _, line := range lines {
		if strings.HasPrefix(line, "# ") {
			continue
		}
		found, name := false, ""
		for _, name = range metrics {
			if !strings.HasPrefix(line, name) {
				continue
			}
			found = true
			break
		}
		if !found || name == "" { // no matched metric found
			continue
		}
		ll := strings.Split(line, " ")
		if len(ll) != 2 {
			continue
		}
		fv, err := strconv.ParseFloat(ll[1], 64)
		if err != nil {
			return nil, fmt.Errorf("failed to parse %q (%w)", ll, err)
		}
		mm[name] = fv
	}
	return mm, nil
}

func getHTTPLines(url string) ([]string, error) {
	req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer cleanlyCloseBody(resp.Body)

	rd := bufio.NewReader(resp.Body)
	lines := []string{}
	for {
		line, err := rd.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return nil, err
		}
		lines = append(lines, strings.TrimSpace(line))
	}
	return lines, nil
}
