package utils

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

var CurlDebug bool

func CurlGet(router string, header map[string]string) ([]byte, error) {
	return curl(http.MethodGet, router, nil, header)
}
func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) {
	return curl_new(http.MethodGet, router, body, header)
}

// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string
func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) {
	return curl(http.MethodPost, router, body, header)
}

func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) {
	return curl(http.MethodPut, router, body, header)
}

// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string
func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) {
	return curl(http.MethodPatch, router, body, header)
}

// CurlDelete is curl delete
func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) {
	return curl(http.MethodDelete, router, body, header)
}

func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) {
	var reqBody io.Reader
	contentType := "application/json"
	switch v := body.(type) {
	case string:
		reqBody = strings.NewReader(v)
	case []byte:
		reqBody = bytes.NewReader(v)
	case map[string]string:
		val := url.Values{}
		for k, v := range v {
			val.Set(k, v)
		}
		reqBody = strings.NewReader(val.Encode())
		contentType = "application/x-www-form-urlencoded"
	case map[string]interface{}:
		val := url.Values{}
		for k, v := range v {
			val.Set(k, v.(string))
		}
		reqBody = strings.NewReader(val.Encode())
		contentType = "application/x-www-form-urlencoded"
	}
	if header == nil {
		header = map[string]string{"Content-Type": contentType}
	}
	if _, ok := header["Content-Type"]; !ok {
		header["Content-Type"] = contentType
	}
	resp, er := CurlReq(method, router, reqBody, header)
	if er != nil {
		return nil, er
	}
	res, err := ioutil.ReadAll(resp.Body)
	if CurlDebug {
		blob := SerializeStr(body)
		if contentType != "application/json" {
			blob = HttpBuild(body)
		}
		fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n",
			router,
			time.Now().Format("2006-01-02 15:04:05.000"),
			method,
			contentType,
			HttpBuildQuery(header),
			blob,
			err,
			SerializeStr(resp.Header),
			string(res),
		)
	}
	resp.Body.Close()
	return res, err
}

func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) {
	var reqBody io.Reader
	contentType := "application/json"

	if header == nil {
		header = map[string]string{"Content-Type": contentType}
	}
	if _, ok := header["Content-Type"]; !ok {
		header["Content-Type"] = contentType
	}
	resp, er := CurlReq(method, router, reqBody, header)
	if er != nil {
		return nil, er
	}
	res, err := ioutil.ReadAll(resp.Body)
	if CurlDebug {
		blob := SerializeStr(body)
		if contentType != "application/json" {
			blob = HttpBuild(body)
		}
		fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n",
			router,
			time.Now().Format("2006-01-02 15:04:05.000"),
			method,
			contentType,
			HttpBuildQuery(header),
			blob,
			err,
			SerializeStr(resp.Header),
			string(res),
		)
	}
	resp.Body.Close()
	return res, err
}

func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) {
	req, _ := http.NewRequest(method, router, reqBody)
	if header != nil {
		for k, v := range header {
			req.Header.Set(k, v)
		}
	}
	// 绕过github等可能因为特征码返回503问题
	// https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/
	defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009,
		0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a}
	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
				CipherSuites:       append(defaultCipherSuites[8:], defaultCipherSuites[:8]...),
			},
		},
		// 获取301重定向
		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
	}
	return client.Do(req)
}

// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序  a=123&b=321
func HttpBuildQuery(args map[string]string, sortAsc ...bool) string {
	str := ""
	if len(args) == 0 {
		return str
	}
	if len(sortAsc) > 0 {
		keys := make([]string, 0, len(args))
		for k := range args {
			keys = append(keys, k)
		}
		if sortAsc[0] {
			sort.Strings(keys)
		} else {
			sort.Sort(sort.Reverse(sort.StringSlice(keys)))
		}
		for _, k := range keys {
			str += "&" + k + "=" + args[k]
		}
	} else {
		for k, v := range args {
			str += "&" + k + "=" + v
		}
	}
	return str[1:]
}

func HttpBuild(body interface{}, sortAsc ...bool) string {
	params := map[string]string{}
	if args, ok := body.(map[string]interface{}); ok {
		for k, v := range args {
			params[k] = AnyToString(v)
		}
		return HttpBuildQuery(params, sortAsc...)
	}
	if args, ok := body.(map[string]string); ok {
		for k, v := range args {
			params[k] = AnyToString(v)
		}
		return HttpBuildQuery(params, sortAsc...)
	}
	if args, ok := body.(map[string]int); ok {
		for k, v := range args {
			params[k] = AnyToString(v)
		}
		return HttpBuildQuery(params, sortAsc...)
	}
	return AnyToString(body)
}
