package dara

import (
	"bytes"
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/alibabacloud-go/debug/debug"
	util "github.com/alibabacloud-go/tea-utils/v2/service"
	"github.com/alibabacloud-go/tea/utils"

	"golang.org/x/net/proxy"
)

type RuntimeOptions = util.RuntimeOptions
type ExtendsParameters = util.ExtendsParameters

var debugLog = debug.Init("dara")

type HttpRequest interface {
}

type HttpResponse interface {
}

type HttpClient interface {
	Call(request *http.Request, transport *http.Transport) (response *http.Response, err error)
}

type daraClient struct {
	sync.Mutex
	httpClient *http.Client
	ifInit     bool
}

func (client *daraClient) Call(request *http.Request, transport *http.Transport) (response *http.Response, err error) {
	response, err = client.httpClient.Do(request)
	return
}

var hookDo = func(fn func(req *http.Request, transport *http.Transport) (*http.Response, error)) func(req *http.Request, transport *http.Transport) (*http.Response, error) {
	return fn
}

var basicTypes = []string{
	"int", "int16", "int64", "int32", "float32", "float64", "string", "bool", "uint64", "uint32", "uint16",
}

// Verify whether the parameters meet the requirements
var validateParams = []string{"require", "pattern", "maxLength", "minLength", "maximum", "minimum", "maxItems", "minItems"}

var clientPool = &sync.Map{}

// Request is used wrap http request
type Request struct {
	Protocol *string
	Port     *int
	Method   *string
	Pathname *string
	Domain   *string
	Headers  map[string]*string
	Query    map[string]*string
	Body     io.Reader
}

// Response is use d wrap http response
type Response struct {
	Body          io.ReadCloser
	StatusCode    *int
	StatusMessage *string
	Headers       map[string]*string
}

// RuntimeObject is used for converting http configuration
type RuntimeObject struct {
	IgnoreSSL         *bool                  `json:"ignoreSSL" xml:"ignoreSSL"`
	ReadTimeout       *int                   `json:"readTimeout" xml:"readTimeout"`
	ConnectTimeout    *int                   `json:"connectTimeout" xml:"connectTimeout"`
	IdleTimeout       *int                   `json:"idleTimeout" xml:"idleTimeout"`
	LocalAddr         *string                `json:"localAddr" xml:"localAddr"`
	HttpProxy         *string                `json:"httpProxy" xml:"httpProxy"`
	HttpsProxy        *string                `json:"httpsProxy" xml:"httpsProxy"`
	NoProxy           *string                `json:"noProxy" xml:"noProxy"`
	MaxIdleConns      *int                   `json:"maxIdleConns" xml:"maxIdleConns"`
	Key               *string                `json:"key" xml:"key"`
	Cert              *string                `json:"cert" xml:"cert"`
	Ca                *string                `json:"ca" xml:"ca"`
	Socks5Proxy       *string                `json:"socks5Proxy" xml:"socks5Proxy"`
	Socks5NetWork     *string                `json:"socks5NetWork" xml:"socks5NetWork"`
	Listener          utils.ProgressListener `json:"listener" xml:"listener"`
	Tracker           *utils.ReaderTracker   `json:"tracker" xml:"tracker"`
	Logger            *utils.Logger          `json:"logger" xml:"logger"`
	RetryOptions      *RetryOptions          `json:"retryOptions" xml:"retryOptions"`
	ExtendsParameters *ExtendsParameters     `json:"extendsParameters,omitempty" xml:"extendsParameters,omitempty"`
	HttpClient
}

func (r *RuntimeObject) getClientTag(domain string) string {
	return strconv.FormatBool(BoolValue(r.IgnoreSSL)) + strconv.Itoa(IntValue(r.ReadTimeout)) +
		strconv.Itoa(IntValue(r.ConnectTimeout)) + strconv.Itoa(IntValue(r.IdleTimeout)) + StringValue(r.LocalAddr) + StringValue(r.HttpProxy) +
		StringValue(r.HttpsProxy) + StringValue(r.NoProxy) + StringValue(r.Socks5Proxy) + StringValue(r.Socks5NetWork) + domain
}

// NewRuntimeObject is used for shortly create runtime object
func NewRuntimeObject(runtime map[string]interface{}) *RuntimeObject {
	if runtime == nil {
		return &RuntimeObject{}
	}

	runtimeObject := &RuntimeObject{
		IgnoreSSL:      TransInterfaceToBool(runtime["ignoreSSL"]),
		ReadTimeout:    TransInterfaceToInt(runtime["readTimeout"]),
		ConnectTimeout: TransInterfaceToInt(runtime["connectTimeout"]),
		IdleTimeout:    TransInterfaceToInt(runtime["idleTimeout"]),
		LocalAddr:      TransInterfaceToString(runtime["localAddr"]),
		HttpProxy:      TransInterfaceToString(runtime["httpProxy"]),
		HttpsProxy:     TransInterfaceToString(runtime["httpsProxy"]),
		NoProxy:        TransInterfaceToString(runtime["noProxy"]),
		MaxIdleConns:   TransInterfaceToInt(runtime["maxIdleConns"]),
		Socks5Proxy:    TransInterfaceToString(runtime["socks5Proxy"]),
		Socks5NetWork:  TransInterfaceToString(runtime["socks5NetWork"]),
		Key:            TransInterfaceToString(runtime["key"]),
		Cert:           TransInterfaceToString(runtime["cert"]),
		Ca:             TransInterfaceToString(runtime["ca"]),
	}
	if runtime["listener"] != nil {
		runtimeObject.Listener = runtime["listener"].(utils.ProgressListener)
	}
	if runtime["tracker"] != nil {
		runtimeObject.Tracker = runtime["tracker"].(*utils.ReaderTracker)
	}
	if runtime["logger"] != nil {
		runtimeObject.Logger = runtime["logger"].(*utils.Logger)
	}
	if runtime["httpClient"] != nil {
		runtimeObject.HttpClient = runtime["httpClient"].(HttpClient)
	}
	if runtime["retryOptions"] != nil {
		runtimeObject.RetryOptions = runtime["retryOptions"].(*RetryOptions)
	}
	return runtimeObject
}

// NewRequest is used shortly create Request
func NewRequest() (req *Request) {
	return &Request{
		Headers: map[string]*string{},
		Query:   map[string]*string{},
	}
}

// NewResponse is create response with http response
func NewResponse(httpResponse *http.Response) (res *Response) {
	res = &Response{}
	res.Body = httpResponse.Body
	res.Headers = make(map[string]*string)
	res.StatusCode = Int(httpResponse.StatusCode)
	res.StatusMessage = String(httpResponse.Status)
	return
}

// Convert is use convert map[string]interface object to struct
func Convert(in interface{}, out interface{}) error {
	byt, _ := json.Marshal(in)
	decoder := jsonParser.NewDecoder(bytes.NewReader(byt))
	decoder.UseNumber()
	err := decoder.Decode(&out)
	return err
}

// ConvertChan converts the source data to the target type and sends it to the specified channel.
// @param src - source data
// @param destChan - target channel
// @return error - error during the conversion process
func ConvertChan(src interface{}, destChan interface{}) error {
	destChanValue := reflect.ValueOf(destChan)
	if destChanValue.Kind() != reflect.Chan {
		return fmt.Errorf("destChan must be a channel")
	}

	if destChanValue.Type().ChanDir() == reflect.SendDir {
		return fmt.Errorf("destChan must be a receive or bidirectional channel")
	}

	elemType := destChanValue.Type().Elem()

	destValue := reflect.New(elemType).Interface()

	err := Convert(src, destValue)
	if err != nil {
		return err
	}
	destValueElem := reflect.ValueOf(destValue).Elem()

	defer func() {
		if r := recover(); r != nil {
		}
	}()

	destChanValue.TrySend(destValueElem)
	return nil
}

// Recover is used to format error
func Recover(in interface{}) error {
	if in == nil {
		return nil
	}
	return errors.New(fmt.Sprint(in))
}

// ReadBody is used read response body
func (response *Response) ReadBody() (body []byte, err error) {
	var buffer [512]byte
	defer response.Body.Close()
	result := bytes.NewBuffer(nil)

	for {
		n, err := response.Body.Read(buffer[0:])
		result.Write(buffer[0:n])
		if err != nil && err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}
	}
	return result.Bytes(), nil
}

func getDaraClient(tag string) *daraClient {
	client, ok := clientPool.Load(tag)
	if client == nil && !ok {
		client = &daraClient{
			httpClient: &http.Client{},
			ifInit:     false,
		}
		clientPool.Store(tag, client)
	}
	return client.(*daraClient)
}

// DoRequest is used send request to server
func DoRequest(request *Request, runtimeObject *RuntimeObject) (response *Response, err error) {
	if runtimeObject == nil {
		runtimeObject = &RuntimeObject{}
	}
	fieldMap := make(map[string]string)
	utils.InitLogMsg(fieldMap)
	defer func() {
		if runtimeObject.Logger != nil {
			runtimeObject.Logger.PrintLog(fieldMap, err)
		}
	}()
	if request.Method == nil {
		request.Method = String("GET")
	}

	if request.Protocol == nil {
		request.Protocol = String("http")
	} else {
		request.Protocol = String(strings.ToLower(StringValue(request.Protocol)))
	}

	requestURL := ""
	request.Domain = request.Headers["host"]
	if request.Port != nil {
		request.Domain = String(fmt.Sprintf("%s:%d", StringValue(request.Domain), IntValue(request.Port)))
	}
	requestURL = fmt.Sprintf("%s://%s%s", StringValue(request.Protocol), StringValue(request.Domain), StringValue(request.Pathname))
	queryParams := request.Query
	// sort QueryParams by key
	q := url.Values{}
	for key, value := range queryParams {
		q.Add(key, StringValue(value))
	}
	querystring := q.Encode()
	if len(querystring) > 0 {
		if strings.Contains(requestURL, "?") {
			requestURL = fmt.Sprintf("%s&%s", requestURL, querystring)
		} else {
			requestURL = fmt.Sprintf("%s?%s", requestURL, querystring)
		}
	}
	debugLog("> %s %s", StringValue(request.Method), requestURL)

	httpRequest, err := http.NewRequest(StringValue(request.Method), requestURL, request.Body)
	if err != nil {
		return
	}
	httpRequest.Host = StringValue(request.Domain)

	var client HttpClient
	if runtimeObject.HttpClient == nil {
		client = getDaraClient(runtimeObject.getClientTag(StringValue(request.Domain)))
	} else {
		client = runtimeObject.HttpClient
	}

	trans, err := getHttpTransport(request, runtimeObject)
	if err != nil {
		return
	}
	if defaultClient, ok := client.(*daraClient); ok {
		defaultClient.Lock()
		if !defaultClient.ifInit || defaultClient.httpClient.Transport == nil {
			defaultClient.httpClient.Transport = trans
		}
		defaultClient.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ConnectTimeout)+IntValue(runtimeObject.ReadTimeout)) * time.Millisecond
		defaultClient.ifInit = true
		defaultClient.Unlock()
	}

	for key, value := range request.Headers {
		if value == nil || key == "content-length" {
			continue
		} else if key == "host" {
			httpRequest.Header["Host"] = []string{*value}
			delete(httpRequest.Header, "host")
		} else if key == "user-agent" {
			httpRequest.Header["User-Agent"] = []string{*value}
			delete(httpRequest.Header, "user-agent")
		} else {
			httpRequest.Header[key] = []string{*value}
		}
		debugLog("> %s: %s", key, StringValue(value))
	}
	contentlength, _ := strconv.Atoi(StringValue(request.Headers["content-length"]))
	event := utils.NewProgressEvent(utils.TransferStartedEvent, 0, int64(contentlength), 0)
	utils.PublishProgress(runtimeObject.Listener, event)

	putMsgToMap(fieldMap, httpRequest)
	startTime := time.Now()
	fieldMap["{start_time}"] = startTime.Format("2006-01-02 15:04:05")
	res, err := hookDo(client.Call)(httpRequest, trans)
	fieldMap["{cost}"] = time.Since(startTime).String()
	completedBytes := int64(0)
	if runtimeObject.Tracker != nil {
		completedBytes = runtimeObject.Tracker.CompletedBytes
	}
	if err != nil {
		event = utils.NewProgressEvent(utils.TransferFailedEvent, completedBytes, int64(contentlength), 0)
		utils.PublishProgress(runtimeObject.Listener, event)
		return
	}

	event = utils.NewProgressEvent(utils.TransferCompletedEvent, completedBytes, int64(contentlength), 0)
	utils.PublishProgress(runtimeObject.Listener, event)

	response = NewResponse(res)
	fieldMap["{code}"] = strconv.Itoa(res.StatusCode)
	fieldMap["{res_headers}"] = Stringify(res.Header)
	debugLog("< HTTP/1.1 %s", res.Status)
	for key, value := range res.Header {
		debugLog("< %s: %s", key, strings.Join(value, ""))
		if len(value) != 0 {
			response.Headers[strings.ToLower(key)] = String(value[0])
		}
	}
	return
}

func DoRequestWithCtx(ctx context.Context, request *Request, runtimeObject *RuntimeObject) (response *Response, err error) {
	if runtimeObject == nil {
		runtimeObject = &RuntimeObject{}
	}
	fieldMap := make(map[string]string)
	utils.InitLogMsg(fieldMap)
	defer func() {
		if runtimeObject.Logger != nil {
			runtimeObject.Logger.PrintLog(fieldMap, err)
		}
	}()
	if request.Method == nil {
		request.Method = String("GET")
	}

	if request.Protocol == nil {
		request.Protocol = String("http")
	} else {
		request.Protocol = String(strings.ToLower(StringValue(request.Protocol)))
	}

	requestURL := ""
	request.Domain = request.Headers["host"]
	if request.Port != nil {
		request.Domain = String(fmt.Sprintf("%s:%d", StringValue(request.Domain), IntValue(request.Port)))
	}
	requestURL = fmt.Sprintf("%s://%s%s", StringValue(request.Protocol), StringValue(request.Domain), StringValue(request.Pathname))
	queryParams := request.Query
	// sort QueryParams by key
	q := url.Values{}
	for key, value := range queryParams {
		q.Add(key, StringValue(value))
	}
	querystring := q.Encode()
	if len(querystring) > 0 {
		if strings.Contains(requestURL, "?") {
			requestURL = fmt.Sprintf("%s&%s", requestURL, querystring)
		} else {
			requestURL = fmt.Sprintf("%s?%s", requestURL, querystring)
		}
	}
	debugLog("> %s %s", StringValue(request.Method), requestURL)

	httpRequest, err := http.NewRequestWithContext(ctx, StringValue(request.Method), requestURL, request.Body)
	if err != nil {
		return
	}
	httpRequest.Host = StringValue(request.Domain)

	var client HttpClient
	if runtimeObject.HttpClient == nil {
		client = getDaraClient(runtimeObject.getClientTag(StringValue(request.Domain)))
	} else {
		client = runtimeObject.HttpClient
	}

	trans, err := getHttpTransport(request, runtimeObject)
	if err != nil {
		return
	}
	if defaultClient, ok := client.(*daraClient); ok {
		defaultClient.Lock()
		if !defaultClient.ifInit || defaultClient.httpClient.Transport == nil {
			defaultClient.httpClient.Transport = trans
		}
		defaultClient.httpClient.Timeout = time.Duration(IntValue(runtimeObject.ConnectTimeout)+IntValue(runtimeObject.ReadTimeout)) * time.Millisecond
		defaultClient.ifInit = true
		defaultClient.Unlock()
	}

	for key, value := range request.Headers {
		if value == nil || key == "content-length" {
			continue
		} else if key == "host" {
			httpRequest.Header["Host"] = []string{*value}
			delete(httpRequest.Header, "host")
		} else if key == "user-agent" {
			httpRequest.Header["User-Agent"] = []string{*value}
			delete(httpRequest.Header, "user-agent")
		} else {
			httpRequest.Header[key] = []string{*value}
		}
		debugLog("> %s: %s", key, StringValue(value))
	}
	contentlength, _ := strconv.Atoi(StringValue(request.Headers["content-length"]))
	event := utils.NewProgressEvent(utils.TransferStartedEvent, 0, int64(contentlength), 0)
	utils.PublishProgress(runtimeObject.Listener, event)

	putMsgToMap(fieldMap, httpRequest)
	startTime := time.Now()
	fieldMap["{start_time}"] = startTime.Format("2006-01-02 15:04:05")
	res, err := hookDo(client.Call)(httpRequest, trans)
	fieldMap["{cost}"] = time.Since(startTime).String()
	completedBytes := int64(0)
	if runtimeObject.Tracker != nil {
		completedBytes = runtimeObject.Tracker.CompletedBytes
	}
	if err != nil {
		select {
		case <-ctx.Done():
			err = TeaSDKError(ctx.Err())
		default:
		}

		event = utils.NewProgressEvent(utils.TransferFailedEvent, completedBytes, int64(contentlength), 0)
		utils.PublishProgress(runtimeObject.Listener, event)
		return
	}

	event = utils.NewProgressEvent(utils.TransferCompletedEvent, completedBytes, int64(contentlength), 0)
	utils.PublishProgress(runtimeObject.Listener, event)

	response = NewResponse(res)
	fieldMap["{code}"] = strconv.Itoa(res.StatusCode)
	fieldMap["{res_headers}"] = Stringify(res.Header)
	debugLog("< HTTP/1.1 %s", res.Status)
	for key, value := range res.Header {
		debugLog("< %s: %s", key, strings.Join(value, ""))
		if len(value) != 0 {
			response.Headers[strings.ToLower(key)] = String(value[0])
		}
	}
	return
}

func getHttpTransport(req *Request, runtime *RuntimeObject) (*http.Transport, error) {
	trans := new(http.Transport)
	trans.ResponseHeaderTimeout = time.Duration(IntValue(runtime.ReadTimeout)) * time.Millisecond
	httpProxy, err := getHttpProxy(StringValue(req.Protocol), StringValue(req.Domain), runtime)
	if err != nil {
		return nil, err
	}
	if strings.ToLower(*req.Protocol) == "https" {
		if BoolValue(runtime.IgnoreSSL) != true {
			trans.TLSClientConfig = &tls.Config{
				InsecureSkipVerify: false,
			}
			if runtime.Key != nil && runtime.Cert != nil && StringValue(runtime.Key) != "" && StringValue(runtime.Cert) != "" {
				cert, err := tls.X509KeyPair([]byte(StringValue(runtime.Cert)), []byte(StringValue(runtime.Key)))
				if err != nil {
					return nil, err
				}
				trans.TLSClientConfig.Certificates = []tls.Certificate{cert}
			}
			if runtime.Ca != nil && StringValue(runtime.Ca) != "" {
				clientCertPool := x509.NewCertPool()
				ok := clientCertPool.AppendCertsFromPEM([]byte(StringValue(runtime.Ca)))
				if !ok {
					return nil, errors.New("Failed to parse root certificate")
				}
				trans.TLSClientConfig.RootCAs = clientCertPool
			}
		} else {
			trans.TLSClientConfig = &tls.Config{
				InsecureSkipVerify: true,
			}
		}
	}
	if httpProxy != nil {
		trans.Proxy = http.ProxyURL(httpProxy)
		if httpProxy.User != nil {
			password, _ := httpProxy.User.Password()
			auth := httpProxy.User.Username() + ":" + password
			basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
			req.Headers["Proxy-Authorization"] = String(basic)
		}
	}
	if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" {
		socks5Proxy, err := getSocks5Proxy(runtime)
		if err != nil {
			return nil, err
		}
		if socks5Proxy != nil {
			var auth *proxy.Auth
			if socks5Proxy.User != nil {
				password, _ := socks5Proxy.User.Password()
				auth = &proxy.Auth{
					User:     socks5Proxy.User.Username(),
					Password: password,
				}
			}
			dialer, err := proxy.SOCKS5(strings.ToLower(StringValue(runtime.Socks5NetWork)), socks5Proxy.Host, auth,
				&net.Dialer{
					Timeout:   time.Duration(IntValue(runtime.ConnectTimeout)) * time.Millisecond,
					DualStack: true,
					LocalAddr: getLocalAddr(StringValue(runtime.LocalAddr)),
				})
			if err != nil {
				return nil, err
			}
			trans.Dial = dialer.Dial
		}
	} else {
		trans.DialContext = setDialContext(runtime)
	}
	if runtime.MaxIdleConns != nil && *runtime.MaxIdleConns > 0 {
		trans.MaxIdleConns = IntValue(runtime.MaxIdleConns)
		trans.MaxIdleConnsPerHost = IntValue(runtime.MaxIdleConns)
	}
	if runtime.IdleTimeout != nil && *runtime.IdleTimeout > 0 {
		trans.IdleConnTimeout = time.Duration(IntValue(runtime.IdleTimeout)) * time.Millisecond
	}
	return trans, nil
}

func putMsgToMap(fieldMap map[string]string, request *http.Request) {
	fieldMap["{host}"] = request.Host
	fieldMap["{method}"] = request.Method
	fieldMap["{uri}"] = request.URL.RequestURI()
	fieldMap["{pid}"] = strconv.Itoa(os.Getpid())
	fieldMap["{version}"] = strings.Split(request.Proto, "/")[1]
	hostname, _ := os.Hostname()
	fieldMap["{hostname}"] = hostname
	fieldMap["{req_headers}"] = Stringify(request.Header)
	fieldMap["{target}"] = request.URL.Path + request.URL.RawQuery
}

func getNoProxy(protocol string, runtime *RuntimeObject) []string {
	var urls []string
	if runtime.NoProxy != nil && StringValue(runtime.NoProxy) != "" {
		urls = strings.Split(StringValue(runtime.NoProxy), ",")
	} else if rawurl := os.Getenv("NO_PROXY"); rawurl != "" {
		urls = strings.Split(rawurl, ",")
	} else if rawurl := os.Getenv("no_proxy"); rawurl != "" {
		urls = strings.Split(rawurl, ",")
	}

	return urls
}

func ToReader(obj interface{}) io.Reader {
	switch obj.(type) {
	case string:
		tmp := obj.(string)
		return strings.NewReader(tmp)
	case *string:
		tmp := obj.(*string)
		return strings.NewReader(StringValue(tmp))
	case []byte:
		return strings.NewReader(string(obj.([]byte)))
	case io.Reader:
		return obj.(io.Reader)
	default:
		panic("Invalid Body. Please set a valid Body.")
	}
}

func ToWriter(obj interface{}) io.Writer {
	switch obj.(type) {
	case string:
		var buf bytes.Buffer
		buf.WriteString(obj.(string))
		return &buf
	case *string:
		var buf bytes.Buffer
		tmp := obj.(*string)
		buf.WriteString(*tmp)
		return &buf
	case []byte:
		var buf bytes.Buffer
		buf.Write(obj.([]byte))
		return &buf
	case io.Writer:
		return obj.(io.Writer)
	case *bytes.Buffer:
		return obj.(*bytes.Buffer)
	case *os.File:
		return obj.(*os.File)
	default:
		panic("Invalid Writer. Please provide a valid Writer.")
	}
}

func ToString(val interface{}) string {
	switch v := val.(type) {
	case []byte:
		return string(v) // 将 []byte 转换为字符串
	default:
		return fmt.Sprintf("%v", v) // 处理其他类型
	}
}

func getHttpProxy(protocol, host string, runtime *RuntimeObject) (proxy *url.URL, err error) {
	urls := getNoProxy(protocol, runtime)
	for _, url := range urls {
		if url == host {
			return nil, nil
		}
	}
	if protocol == "https" {
		if runtime.HttpsProxy != nil && StringValue(runtime.HttpsProxy) != "" {
			proxy, err = url.Parse(StringValue(runtime.HttpsProxy))
		} else if rawurl := os.Getenv("HTTPS_PROXY"); rawurl != "" {
			proxy, err = url.Parse(rawurl)
		} else if rawurl := os.Getenv("https_proxy"); rawurl != "" {
			proxy, err = url.Parse(rawurl)
		}
	} else {
		if runtime.HttpProxy != nil && StringValue(runtime.HttpProxy) != "" {
			proxy, err = url.Parse(StringValue(runtime.HttpProxy))
		} else if rawurl := os.Getenv("HTTP_PROXY"); rawurl != "" {
			proxy, err = url.Parse(rawurl)
		} else if rawurl := os.Getenv("http_proxy"); rawurl != "" {
			proxy, err = url.Parse(rawurl)
		}
	}

	return proxy, err
}

func getSocks5Proxy(runtime *RuntimeObject) (proxy *url.URL, err error) {
	if runtime.Socks5Proxy != nil && StringValue(runtime.Socks5Proxy) != "" {
		proxy, err = url.Parse(StringValue(runtime.Socks5Proxy))
	}
	return proxy, err
}

func getLocalAddr(localAddr string) (addr *net.TCPAddr) {
	if localAddr != "" {
		addr = &net.TCPAddr{
			IP: net.ParseIP(localAddr),
		}
	}
	return addr
}

func setDialContext(runtime *RuntimeObject) func(cxt context.Context, net, addr string) (c net.Conn, err error) {
	return func(ctx context.Context, network, address string) (net.Conn, error) {
		timeout := time.Duration(IntValue(runtime.ConnectTimeout)) * time.Millisecond
		dialer := &net.Dialer{
			Timeout: timeout,
			Resolver: &net.Resolver{
				PreferGo: false,
			},
		}
		if runtime.LocalAddr != nil && StringValue(runtime.LocalAddr) != "" {
			dialer.LocalAddr = getLocalAddr(StringValue(runtime.LocalAddr))
		}
		return dialer.DialContext(ctx, network, address)
	}
}

func ToObject(obj interface{}) map[string]interface{} {
	result := make(map[string]interface{})
	byt, _ := json.Marshal(obj)
	err := json.Unmarshal(byt, &result)
	if err != nil {
		return nil
	}
	return result
}

func AllowRetry(retry interface{}, retryTimes *int) *bool {
	if IntValue(retryTimes) == 0 {
		return Bool(true)
	}
	retryMap, ok := retry.(map[string]interface{})
	if !ok {
		return Bool(false)
	}
	retryable, ok := retryMap["retryable"].(bool)
	if !ok || !retryable {
		return Bool(false)
	}

	maxAttempts, ok := retryMap["maxAttempts"].(int)
	if !ok || maxAttempts < IntValue(retryTimes) {
		return Bool(false)
	}
	return Bool(true)
}

func Merge(args ...interface{}) map[string]*string {
	finalArg := make(map[string]*string)
	for _, obj := range args {
		switch obj.(type) {
		case map[string]*string:
			arg := obj.(map[string]*string)
			for key, value := range arg {
				if value != nil {
					finalArg[key] = value
				}
			}
		default:
			byt, _ := json.Marshal(obj)
			arg := make(map[string]string)
			err := json.Unmarshal(byt, &arg)
			if err != nil {
				return finalArg
			}
			for key, value := range arg {
				if value != "" {
					finalArg[key] = String(value)
				}
			}
		}
	}

	return finalArg
}

func IsNil(val interface{}) bool {
	defer func() {
		recover()
	}()
	if val == nil {
		return true
	}

	v := reflect.ValueOf(val)
	if v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map {
		return v.IsNil()
	}

	valType := reflect.TypeOf(val)
	valZero := reflect.Zero(valType)
	return valZero == v
}

func isNil(a interface{}) bool {
	defer func() {
		recover()
	}()
	vi := reflect.ValueOf(a)
	return vi.IsNil()
}

func isNilOrZero(value interface{}) bool {
	if value == nil {
		return true
	}

	v := reflect.ValueOf(value)
	switch v.Kind() {
	case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
		return v.IsNil()
	default:
		// Check for zero value
		return reflect.DeepEqual(value, reflect.Zero(v.Type()).Interface())
	}
}

func Default(inputValue interface{}, defaultValue interface{}) (_result interface{}) {
	if isNilOrZero(inputValue) {
		_result = defaultValue
		return _result
	}

	_result = inputValue
	return _result
}

func ToMap(args ...interface{}) map[string]interface{} {
	isNotNil := false
	finalArg := make(map[string]interface{})
	for _, obj := range args {
		if obj == nil {
			continue
		}

		if isNil(obj) {
			continue
		}
		isNotNil = true

		switch obj.(type) {
		case map[string]*string:
			arg := obj.(map[string]*string)
			for key, value := range arg {
				if value != nil {
					finalArg[key] = StringValue(value)
				}
			}
		case map[string]interface{}:
			arg := obj.(map[string]interface{})
			for key, value := range arg {
				if value != nil {
					finalArg[key] = value
				}
			}
		case *string:
			str := obj.(*string)
			arg := make(map[string]interface{})
			err := json.Unmarshal([]byte(StringValue(str)), &arg)
			if err == nil {
				for key, value := range arg {
					if value != nil {
						finalArg[key] = value
					}
				}
			}
			tmp := make(map[string]string)
			err = json.Unmarshal([]byte(StringValue(str)), &tmp)
			if err == nil {
				for key, value := range arg {
					if value != "" {
						finalArg[key] = value
					}
				}
			}
		case []byte:
			byt := obj.([]byte)
			arg := make(map[string]interface{})
			err := json.Unmarshal(byt, &arg)
			if err == nil {
				for key, value := range arg {
					if value != nil {
						finalArg[key] = value
					}
				}
				break
			}
		default:
			val := reflect.ValueOf(obj)
			if val.Kind().String() == "map" {
				tmp := val.MapKeys()
				for _, key := range tmp {
					finalArg[key.String()] = val.MapIndex(key).Interface()
				}
			} else {
				res := structToMap(val)
				for key, value := range res {
					if value != nil {
						finalArg[key] = value
					}
				}
			}
		}
	}

	if !isNotNil {
		return nil
	}
	return finalArg
}

func structToMap(dataValue reflect.Value) map[string]interface{} {
	out := make(map[string]interface{})
	if !dataValue.IsValid() {
		return out
	}
	if dataValue.Kind().String() == "ptr" {
		if dataValue.IsNil() {
			return out
		}
		dataValue = dataValue.Elem()
	}
	if !dataValue.IsValid() {
		return out
	}
	dataType := dataValue.Type()
	if dataType.Kind().String() != "struct" {
		return out
	}
	for i := 0; i < dataType.NumField(); i++ {
		field := dataType.Field(i)
		name, containsNameTag := field.Tag.Lookup("json")
		if !containsNameTag {
			name = field.Name
		} else {
			strs := strings.Split(name, ",")
			name = strs[0]
		}
		fieldValue := dataValue.FieldByName(field.Name)
		if !fieldValue.IsValid() || fieldValue.IsNil() {
			continue
		}
		if field.Type.String() == "io.Reader" || field.Type.String() == "io.Writer" {
			continue
		} else if field.Type.Kind().String() == "struct" {
			out[name] = structToMap(fieldValue)
		} else if field.Type.Kind().String() == "ptr" &&
			field.Type.Elem().Kind().String() == "struct" {
			if fieldValue.Elem().IsValid() {
				out[name] = structToMap(fieldValue)
			}
		} else if field.Type.Kind().String() == "ptr" {
			if fieldValue.IsValid() && !fieldValue.IsNil() {
				out[name] = fieldValue.Elem().Interface()
			}
		} else if field.Type.Kind().String() == "slice" {
			tmp := make([]interface{}, 0)
			num := fieldValue.Len()
			for i := 0; i < num; i++ {
				value := fieldValue.Index(i)
				if !value.IsValid() {
					continue
				}
				if value.Type().Kind().String() == "ptr" &&
					value.Type().Elem().Kind().String() == "struct" {
					if value.IsValid() && !value.IsNil() {
						tmp = append(tmp, structToMap(value))
					}
				} else if value.Type().Kind().String() == "struct" {
					tmp = append(tmp, structToMap(value))
				} else if value.Type().Kind().String() == "ptr" {
					if value.IsValid() && !value.IsNil() {
						tmp = append(tmp, value.Elem().Interface())
					}
				} else {
					tmp = append(tmp, value.Interface())
				}
			}
			if len(tmp) > 0 {
				out[name] = tmp
			}
		} else {
			out[name] = fieldValue.Interface()
		}

	}
	return out
}

func GetBackoffTime(backoff interface{}, retrytimes *int) *int {
	backoffMap, ok := backoff.(map[string]interface{})
	if !ok {
		return Int(0)
	}
	policy, ok := backoffMap["policy"].(string)
	if !ok || policy == "no" {
		return Int(0)
	}

	period, ok := backoffMap["period"].(int)
	if !ok || period == 0 {
		return Int(0)
	}

	maxTime := math.Pow(2.0, float64(IntValue(retrytimes)))
	return Int(rand.Intn(int(maxTime-1)) * period)
}

func Sleep(backoffTime int) {
	sleeptime := time.Duration(backoffTime) * time.Second
	time.Sleep(sleeptime)
}

// Determines whether realType is in filterTypes
func isFilterType(realType string, filterTypes []string) bool {
	for _, value := range filterTypes {
		if value == realType {
			return true
		}
	}
	return false
}

func TransInterfaceToBool(val interface{}) *bool {
	if val == nil {
		return nil
	}

	return Bool(val.(bool))
}

func TransInterfaceToInt(val interface{}) *int {
	if val == nil {
		return nil
	}

	return Int(val.(int))
}

func TransInterfaceToString(val interface{}) *string {
	if val == nil {
		return nil
	}

	return String(val.(string))
}

func Prettify(i interface{}) string {
	resp, _ := json.MarshalIndent(i, "", "   ")
	return string(resp)
}

func ToInt(a *int32) *int {
	return Int(int(Int32Value(a)))
}

func ToInt32(a *int) *int32 {
	return Int32(int32(IntValue(a)))
}

func ToBytes(s, encodingType string) []byte {
	switch encodingType {
	case "utf8":
		return []byte(s)
	case "base64":
		data, err := base64.StdEncoding.DecodeString(s)
		if err != nil {
			return nil
		}
		return data
	case "hex":
		data, err := hex.DecodeString(s)
		if err != nil {
			return nil
		}
		return data
	default:
		return nil
	}
}

func BytesFromString(str string, typeStr string) []byte {
	switch typeStr {
	case "utf8":
		return []byte(str)
	case "hex":
		bytes, err := hex.DecodeString(str)
		if err == nil {
			return bytes
		}
	case "base64":
		bytes, err := base64.StdEncoding.DecodeString(str)
		if err == nil {
			return bytes
		}
	}

	// 对于不支持的类型或解码失败，返回 nil
	return nil
}

func ForceInt(a interface{}) int {
	num, _ := a.(int)
	return num
}

func ForceBoolean(a interface{}) bool {
	b, _ := a.(bool)
	return b
}

func ForceInt64(a interface{}) int64 {
	b, _ := a.(int64)
	return b
}

func ForceUint64(a interface{}) uint64 {
	b, _ := a.(uint64)
	return b
}

// ForceInt32 attempts to assert that a is of type int32.
func ForceInt32(a interface{}) int32 {
	b, _ := a.(int32)
	return b
}

// ForceUInt32 attempts to assert that a is of type uint32.
func ForceUInt32(a interface{}) uint32 {
	b, _ := a.(uint32)
	return b
}

// ForceInt16 attempts to assert that a is of type int16.
func ForceInt16(a interface{}) int16 {
	b, _ := a.(int16)
	return b
}

// ForceUInt16 attempts to assert that a is of type uint16.
func ForceUInt16(a interface{}) uint16 {
	b, _ := a.(uint16)
	return b
}

// ForceInt8 attempts to assert that a is of type int8.
func ForceInt8(a interface{}) int8 {
	b, _ := a.(int8)
	return b
}

// ForceUInt8 attempts to assert that a is of type uint8.
func ForceUInt8(a interface{}) uint8 {
	b, _ := a.(uint8)
	return b
}

// ForceFloat32 attempts to assert that a is of type float32.
func ForceFloat32(a interface{}) float32 {
	b, _ := a.(float32)
	return b
}

// ForceFloat64 attempts to assert that a is of type float64.
func ForceFloat64(a interface{}) float64 {
	b, _ := a.(float64)
	return b
}
