// 适用于大量http(s)请求，连接复用
package webUtil

import (
	"bytes"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptrace"
	"net/url"
	"time"
)

type Client struct {
	traceCtx context.Context
	client   *http.Client
}

type TransportOPT struct {
	//超时时间
	Timeout time.Duration

	//代理字符串，如"http://127.0.0.1:1080"
	ProxyString string

	//最大保持连接数
	MaxIdleConns int

	//每个主机的最大保持连接数
	MaxIdleConnsPerHost int

	//单主机最大连接数
	MaxConnsPerHost int

	//保持连接的超时时间
	IdleConnTimeout time.Duration

	//禁止保持连接
	DisableKeepAlives bool
}

// GET请求
func (c *Client) Get(urlStr string, header map[string]string) (result []byte, err error) {
	req, err := http.NewRequestWithContext(c.traceCtx, http.MethodGet, urlStr, nil)
	if err != nil {
		//fmt.Println(err)
		return
	}

	// 处理头部（包括默认头部，以及传入的头部集合）
	if header == nil {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	} else {
		for k, v := range header {
			req.Header.Add(k, v)
		}
	}

	res, err := c.client.Do(req)
	if err != nil {
		//fmt.Println(err)
		return
	}
	defer res.Body.Close()

	result, err = ioutil.ReadAll(res.Body)

	return
}

// POST请求
func (c *Client) PostWithMap(urlStr string, data map[string]string, header map[string]string) (result []byte, err error) {
	// 组装POST数据
	postValues := url.Values{}
	for key, value := range data {
		postValues.Set(key, value)
	}
	postDataStr := postValues.Encode()
	byteData := []byte(postDataStr)

	var req *http.Request
	req, err = http.NewRequestWithContext(c.traceCtx, http.MethodPost, urlStr, bytes.NewReader(byteData))
	if err != nil {
		//fmt.Println(err)
		return
	}

	// 处理头部（包括默认头部，以及传入的头部集合）
	if header == nil {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	} else {
		for k, v := range header {
			req.Header.Add(k, v)
		}
	}

	var res *http.Response
	res, err = c.client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	result, err = ioutil.ReadAll(res.Body)

	return
}

// POST请求
func (c *Client) PostWithByte(urlStr string, data []byte, header map[string]string) (result []byte, err error) {
	var req *http.Request
	req, err = http.NewRequestWithContext(c.traceCtx, http.MethodPost, urlStr, bytes.NewReader(data))
	if err != nil {
		//fmt.Println(err)
		return
	}

	// 处理头部（包括默认头部，以及传入的头部集合）
	if header == nil {
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	} else {
		for k, v := range header {
			req.Header.Add(k, v)
		}
	}

	var res *http.Response
	res, err = c.client.Do(req)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer res.Body.Close()

	result, err = ioutil.ReadAll(res.Body)

	return
}

// 新建Client对象
// transportOPT参数说明
//
//	Timeout - 连接绝对超时时间
//	ProxyString - 代理字符串
//	MaxIdleConns - 最大空闲连接数
//	MaxIdleConnsPerHost - 每个目标主机的最大空闲连接数
//	MaxConnsPerHost - 每个目标主机的最大连接数
//	IdleConnTimeout - 空闲连接超时时间
//	DisableKeepAlives - 禁用连接保持(要使用连接复用，此值不传入或传入false；否则连接可能不会复用)
func NewClient(transportOPT *TransportOPT) (c *Client) {
	c = &Client{}

	//代理
	getProxy := func() func(*http.Request) (*url.URL, error) {
		if (transportOPT != nil) && len(transportOPT.ProxyString) > 0 {
			uri, _ := url.Parse(transportOPT.ProxyString)
			return http.ProxyURL(uri)
		}

		return nil
	}

	// 默认参数
	timeout := 30 * time.Second
	maxIdleConns := 60000
	maxIdleConnsPerHost := 30000
	maxConnsPerHost := 30000
	idleConnTimeout := time.Minute * 1
	disableKeepAlives := false

	if transportOPT != nil {
		// 根据传入参数修改默认参数
		if transportOPT.Timeout > 0 {
			timeout = transportOPT.Timeout
		}
		if transportOPT.MaxIdleConns > 0 {
			maxIdleConns = transportOPT.MaxIdleConns
		}
		if transportOPT.MaxIdleConnsPerHost > 0 {
			maxIdleConnsPerHost = transportOPT.MaxIdleConnsPerHost
		}
		if transportOPT.MaxConnsPerHost > 0 {
			maxConnsPerHost = transportOPT.MaxConnsPerHost
		}
		if transportOPT.IdleConnTimeout > 0 {
			idleConnTimeout = transportOPT.IdleConnTimeout
		}

		disableKeepAlives = transportOPT.DisableKeepAlives
	}

	clientTrace := &httptrace.ClientTrace{
		//GotConn: func(gci httptrace.GotConnInfo) {
		//	fmt.Printf("conn was reused: %t\n", gci.Reused)
		//},
	}

	c.traceCtx = httptrace.WithClientTrace(context.Background(), clientTrace)
	c.client = &http.Client{
		Timeout: timeout,

		Transport: &http.Transport{
			Proxy:               getProxy(),
			MaxIdleConns:        maxIdleConns,
			MaxIdleConnsPerHost: maxIdleConnsPerHost,
			MaxConnsPerHost:     maxConnsPerHost,
			IdleConnTimeout:     idleConnTimeout,
			DisableKeepAlives:   disableKeepAlives,
			// ForceAttemptHTTP2:   true,
		},
	}

	return
}
