/*
 *FileName:   simpleRequest.go
 *Author:		JJXu
 *CreateTime:	2022/3/2 上午12:33
 *Description:
 */

package simpleRequest

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

func NewRequest(opts ...OPTION) *SimpleRequest {
	var (
		hd = http.Header{}
		qp = url.Values{}
	)

	var r = &SimpleRequest{
		//headers: make(map[string]string),
		//cookies: make(map[string]string),
		timeout:          time.Second * 7,
		queryParams:      qp,
		headers:          hd,
		BodyEntries:      make(map[string]any),
		bodyEntryParsers: bodyEntryParsers,
	}
	if len(opts) > 0 {
		for _, o := range opts {
			r = o(r)
		}
	}
	return r
}

type SimpleRequest struct {
	url            string
	queryParams    url.Values
	body           io.Reader
	headers        http.Header
	omitHeaderKeys []string
	transport      *http.Transport

	BodyEntryMark    EntryMark
	BodyEntries      map[string]any
	bodyEntryParsers map[string]IBodyEntryParser

	timeout time.Duration

	Response http.Response //用于获取完整的返回内容。请注意要在请求之后才能获取
	Request  http.Request  //用于获取完整的请求内容。请注意要在请求之后才能获取
	//cookies           map[string]string
	//data              any
	//cli               *http.Client
	//debug             bool
	//method            string
	//time              int64
	//disableKeepAlives bool
	//tlsClientConfig   *tls.Config
	//jar               http.CookieJar
	//proxy             func(*http.Request) (*url.URL, error)
	//checkRedirect     func(req *http.Request, via []*http.Request) error
}

//func (s *SimpleRequest) NewRequest() *SimpleRequest {
//	var qp = url.Values{}
//	return &SimpleRequest{
//		//headers: make(map[string]string),
//		//cookies: make(map[string]string),
//		timeout:     time.Second * 7,
//		queryParams: qp,
//		BodyEntries:   make(map[string]any),
//	}
//}

//------------------------------------------------------
//
//						数据准备

// Authorization 添加令牌的方法集合
func (s *SimpleRequest) Authorization() *Authorization {
	return &Authorization{
		simpleReq: s,
	}
}

// Headers 添加请求头
func (s *SimpleRequest) Headers() *HeadersConf {
	return &HeadersConf{
		simpleReq: s,
	}
}

// Body 添加请求体
func (s *SimpleRequest) Body() *BodyConf {
	return &BodyConf{
		simpleReq: s,
	}
}

// QueryParams 添加url后面的参数
func (s *SimpleRequest) QueryParams() *QueryParams {
	return &QueryParams{
		simpleReq: s,
	}
}

// 跳过证书验证
func (s *SimpleRequest) SkipCertVerify() *SimpleRequest {

	s.transport = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	return s
}

// 设置超时时间
func (s *SimpleRequest) TimeOut(t time.Duration) *SimpleRequest {
	s.timeout = t
	return s
}

// ------------------------------------------------------
//
//	发送请求
//
// 发送postt请求
func (s *SimpleRequest) do(request *http.Request) (body []byte, err error) {
	//3. 建立http客户端
	client := &http.Client{
		Timeout: s.timeout,
	}
	if s.transport != nil {
		client.Transport = s.transport
	}
	//3.1 发送数据
	resp, err := client.Do(request)
	if err != nil {
		fmt.Println("【Request Error】:", err.Error())
		return
	}

	//v0.0.2更新,将request和response内容返回,便于用户进行分析 JJXu 03-11-2022
	if resp != nil {
		s.Response = *resp
	} else {
		return
	}
	if request != nil {
		s.Request = *request
	}

	defer resp.Body.Close()
	//3.2 获取数据
	body, err = io.ReadAll(resp.Body)
	return
}

// POST method does POST HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) POST(urls string) (body []byte, err error) {
	return s.LaunchTo(urls, http.MethodPost)
}

// GET method does GET HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) GET(urls string) (body []byte, err error) {
	return s.LaunchTo(urls, http.MethodGet)
}

// 通用的请求方法
func (s *SimpleRequest) LaunchTo(urls, method string) (body []byte, err error) {
	// body
	s.initBody()
	r, err := http.NewRequest(method, urls, s.body)
	if err != nil {
		return nil, err
	}
	//headers
	//for k := range s.headers {
	//	r.Header[k] = append(r.Header[k], s.headers[k]...)
	//	s.headers.Del(k)
	//}
	r.Header = s.headers
	for _, k := range s.omitHeaderKeys {
		r.Header.Del(k)
	}
	//queryParams
	if r.URL.RawQuery != "" {
		values, err := url.ParseQuery(r.URL.RawQuery)
		if err == nil {
			newValues := url.Values{}
			for k := range values {
				newValues.Set(k, values.Get(k))
			}
			for k := range s.queryParams {
				newValues.Set(k, s.queryParams.Get(k))
			}
			s.queryParams = newValues
		}
	}
	r.URL.RawQuery = s.queryParams.Encode()

	body, err = s.do(r)
	return
}

// PUT method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231.
func (s *SimpleRequest) PUT(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodPut)
}

// DELETE method does DELETE HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) DELETE(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodDelete)
}

// Patch method does Patch HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) PATCH(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodPatch)
}

// HEAD method does HEAD HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) HEAD(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodHead)
}

// CONNECT method does CONNECT HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) CONNECT(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodConnect)
}

// OPTIONS method does OPTIONS HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) OPTIONS(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodOptions)
}

// TRACE method does TRACE HTTP request. It's defined in section 2 of RFC5789.
func (s *SimpleRequest) TRACE(url string) (body []byte, err error) {
	return s.LaunchTo(url, http.MethodTrace)
}

// ------------------------------------------------------
//
//	这里数据
func (s *SimpleRequest) initBody() {
	contentTypeData := s.headers.Get(hdrContentTypeKey)
	switch {
	case IsJSONType(contentTypeData):
		var parser, ok = s.bodyEntryParsers[jsonContentType]
		if !ok {
			parser = new(JsonParser)
		}
		s.body = parser.Unmarshal(s.BodyEntryMark, s.BodyEntries)

	case strings.Contains(contentTypeData, formDataType):
		var parser, ok = s.bodyEntryParsers[formDataType]
		if !ok {
			parser = new(FormDataParser)
		}
		s.body = parser.Unmarshal(s.BodyEntryMark, s.BodyEntries)
		fdParser := parser.(*FormDataParser)
		s.headers.Set("Content-Type", fdParser.ContentType)

	case IsXMLType(contentTypeData):
		//application/soap+xml ,application/xml
		var parser, ok = s.bodyEntryParsers[xmlDataType]
		if !ok {
			data, _ := s.BodyEntries[StringEntryType.string()].(string)
			s.body = strings.NewReader(data)
			return
		}
		s.body = parser.Unmarshal(s.BodyEntryMark, s.BodyEntries)

	case strings.Contains(contentTypeData, "text") || strings.Contains(contentTypeData, javaScriptType):
		var parser, ok = s.bodyEntryParsers[textPlainType]
		if !ok {
			data, _ := s.BodyEntries[StringEntryType.string()].(string)
			s.body = strings.NewReader(data)
			return
		}
		s.body = parser.Unmarshal(s.BodyEntryMark, s.BodyEntries)

	case contentTypeData == "" && s.BodyEntryMark == BytesEntryType:
		s.body = bytes.NewReader(s.BodyEntries[BytesEntryType.string()].([]byte))

	case contentTypeData == "" || strings.Contains(contentTypeData, "form-urlencoded"):
		//default header type is "x-www-form-urlencoded"
		var parser, ok = s.bodyEntryParsers["form-urlencoded"]
		if !ok {
			tmpData := url.Values{}
			for k, v := range s.BodyEntries {
				tmpData.Set(k, fmt.Sprintf("%v", v))
			}
			s.body = strings.NewReader(tmpData.Encode())
			s.Headers().ConentType_formUrlencoded()
			return
		}
		s.body = parser.Unmarshal(s.BodyEntryMark, s.BodyEntries)
	default:
		//todo Automatically determine the data type
		tmpData := url.Values{}
		for k, v := range tmpData {
			tmpData.Set(k, fmt.Sprintf("%v", v))
		}
		s.body = strings.NewReader(tmpData.Encode())
	}
}