package vm

import (
	"compress/gzip"
	"context"
	"errors"
	"io"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/pingcap/log"
	"go.uber.org/zap"
)

const DefaultTimeoutSecs = 10

func (vq *VMQuery) vmQuery(ctx context.Context, queryType, query string, timeSecs int, addrP *addrParams) ([]byte, error) {
	timeoutSecs := buildTimeoutSecs(ctx)
	if timeoutSecs <= 0 {
		return nil, errors.New("timeout")
	}

	queries := map[string]string{
		"query":   query,
		"time":    strconv.Itoa(timeSecs),
		"timeout": strconv.Itoa(timeoutSecs),
	}
	queryURL, err := vq.buildVMURL(addrP, pathQuery)
	if err != nil {
		return nil, err
	}
	return postVMQuery(ctx, vq.client, queryType, queryURL, queries)
}

func (vq *VMQuery) vmQueryRange(ctx context.Context, queryType, query string, startSecs, endSecs, stepSecs int, addrP *addrParams) ([]byte, error) {
	timeoutSecs := buildTimeoutSecs(ctx)
	if timeoutSecs <= 0 {
		return nil, errors.New("timeout")
	}

	queries := map[string]string{
		"query":   query,
		"start":   strconv.Itoa(startSecs),
		"end":     strconv.Itoa(endSecs),
		"step":    strconv.Itoa(stepSecs),
		"timeout": strconv.Itoa(timeoutSecs),
	}
	queryURL, err := vq.buildVMURL(addrP, pathQueryRange)
	if err != nil {
		return nil, err
	}
	return postVMQuery(ctx, vq.client, queryType, queryURL, queries)
}

func postVMQuery(ctx context.Context, client *http.Client, queryType, queryURL string, queries map[string]string) ([]byte, error) {
	t := time.Now()
	statusCode := 200 // default status code, may be overwritten by the actual status code
	defer func() {
		dur := time.Since(t)
		vmDurationSeconds.WithLabelValues(queryType, strconv.Itoa(statusCode)).Observe(dur.Seconds())
	}()

	reqQueries := url.Values{}
	for k, v := range queries {
		reqQueries.Set(k, v)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", queryURL, strings.NewReader(reqQueries.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Accept-Encoding", "gzip")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body := resp.Body
	if resp.Header.Get("Content-Encoding") == "gzip" {
		body, err = gzip.NewReader(resp.Body)
		if err != nil {
			return nil, err
		}
	}

	statusCode = resp.StatusCode
	if statusOK := statusCode >= 200 && statusCode < 300; !statusOK {
		errMsg, _ := io.ReadAll(body)
		errStr := string(errMsg)
		log.Warn("failed to fetch timeseries db", zap.String("error", errStr))
		return nil, errors.New(errStr)
	}

	return io.ReadAll(body)
}

func buildTimeoutSecs(ctx context.Context) int {
	timeoutSecs := DefaultTimeoutSecs
	ddl, ok := ctx.Deadline()
	if ok {
		timeoutSecs = int(math.Ceil(time.Until(ddl).Seconds()))
	}
	return timeoutSecs
}
