package http

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/gin-gonic/gin/binding"
	"github.com/pingcap/errors"
	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

// @Time    : 2024/8/27 15:28
// @Author  : lvhongmeng

// the timeout of frontend is 30s, so we set the timeout to 27s
const defaultTimeout = 27 * time.Second

type HttpRequester interface {
	RequestWithHeaders(ctx context.Context, method, url string, req any, extraHeaders map[string]string) (int, []byte, error)
	SetHttpTimeout(t time.Duration)
	Clone() HttpRequester
}

type AgentHttpRequester struct {
	Doer *http.Client
}

func NewAgentHttpRequester() *AgentHttpRequester {
	cli := http.DefaultClient
	tracedClient := &http.Client{
		Transport: otelhttp.NewTransport(
			cli.Transport,
			otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
				return fmt.Sprintf("%s %s %s", "tcidm", r.Method, r.URL.Path)
			}),
		),
		Timeout:       cli.Timeout,
		CheckRedirect: cli.CheckRedirect,
		Jar:           cli.Jar,
	}

	return &AgentHttpRequester{
		Doer: tracedClient,
	}
}

func (a *AgentHttpRequester) RequestWithHeaders(ctx context.Context, method, url string, req any, extraHeaders map[string]string) (int, []byte, error) {
	var (
		payload = []byte("")
		err     error
	)
	if req != nil {
		payload, err = json.Marshal(req)
		if err != nil {
			return 0, nil, errors.Trace(err)
		}
	}
	httpReq, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(payload))
	if err != nil {
		return 0, nil, errors.Trace(err)
	}
	httpReq.Header.Set("Content-Type", binding.MIMEJSON)
	for k, v := range extraHeaders {
		httpReq.Header.Set(k, v)
	}
	httpResp, err := a.Doer.Do(httpReq)
	if err != nil {
		return 0, nil, errors.Trace(err)
	}
	defer httpResp.Body.Close() // nolint: errcheck

	respBytes, err := io.ReadAll(httpResp.Body)
	if err != nil {
		return 0, nil, errors.Trace(err)
	}

	return httpResp.StatusCode, respBytes, nil
}

func (a *AgentHttpRequester) SetHttpTimeout(t time.Duration) {
	a.Doer.Timeout = t
}

func (a *AgentHttpRequester) Clone() HttpRequester {
	doer := *a.Doer
	return &AgentHttpRequester{
		Doer: &doer,
	}
}
