package util

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"log"
	"net/http"
	neturl "net/url"
	"strings"
	"time"
)

func HttpRequest(method, url string,
	header map[string]string, params interface{},
	body interface{}) (*http.Request, error) {
	var bodyReader io.Reader
	if body != nil {
		switch body := body.(type) {
		case neturl.Values:
			bodyReader = strings.NewReader(body.Encode())
		default:
			bodyBytes, err := json.Marshal(body)
			if err != nil {
				return nil, err
			}
			bodyReader = bytes.NewReader(bodyBytes)
		}
	}

	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		return nil, err
	}

	for k, v := range header {
		req.Header.Add(k, v)
	}

	if params != nil {
		params := params.(map[string]string)
		q := req.URL.Query()
		for k, v := range params {
			q.Add(k, v)
		}
		req.URL.RawQuery = q.Encode()
	}

	return req, err
}

type options func(cli *http.Client)

func SetHttpTimeout(duration time.Duration) options {
	return func(cli *http.Client) {
		cli.Timeout = duration
	}
}

func Do(req *http.Request, obj interface{}, opts ...options) error {
	log.Println("http do ", req.URL)
	cli := http.DefaultClient
	for _, opt := range opts {
		opt(cli)
	}

	resp, err := cli.Do(req)
	if err != nil {
		return err
	}

	defer resp.Body.Close()
	bodyBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
		return errors.New("http response failed :" + ByteSliceToString(bodyBytes))
	}

	switch d := obj.(type) {
	case *string:
		*d = string(bodyBytes)
	default:
		err = json.Unmarshal(bodyBytes, obj)
		if err != nil {
			log.Println("Do() error resp = ", string(bodyBytes))
		}
	}
	return err
}
