// Copyright (c) 2015-2023 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.
// resty source code and usage is governed by a MIT style
// license that can be found in the LICENSE file.

package resty

import (
	
	
	
	
	
	
	
	
	
	
	
	
	
)

const debugRequestLogKey = "__restyDebugRequestLog"

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Request Middleware(s)
//_______________________________________________________________________

func parseRequestURL( *Client,  *Request) error {
	if  := len(.PathParams) + len(.RawPathParams) + len(.PathParams) + len(.RawPathParams);  > 0 {
		 := make(map[string]string, )

		// GitHub #103 Path Params
		for ,  := range .PathParams {
			[] = url.PathEscape()
		}
		for ,  := range .PathParams {
			if ,  := []; ! {
				[] = url.PathEscape()
			}
		}

		// GitHub #663 Raw Path Params
		for ,  := range .RawPathParams {
			if ,  := []; ! {
				[] = 
			}
		}
		for ,  := range .RawPathParams {
			if ,  := []; ! {
				[] = 
			}
		}

		if len() > 0 {
			var  int
			 := acquireBuffer()
			defer releaseBuffer()
			// search for the next or first opened curly bracket
			for  := strings.Index(.URL, "{");  > ;  =  + strings.Index(.URL[:], "{") {
				// write everything form the previous position up to the current
				if  >  {
					.WriteString(.URL[:])
				}
				// search for the closed curly bracket from current position
				 :=  + strings.Index(.URL[:], "}")
				// if not found, then write the remainder and exit
				if  <  {
					.WriteString(.URL[:])
					 = len(.URL)
					break
				}
				// special case for {}, without parameter's name
				if  == +1 {
					.WriteString("{}")
				} else {
					// check for the replacement
					 := .URL[+1 : ]
					,  := []
					/// keep the original string if the replacement not found
					if ! {
						 = .URL[ : +1]
					}
					.WriteString()
				}

				// set the previous position after the closed curly bracket
				 =  + 1
				if  >= len(.URL) {
					break
				}
			}
			if .Len() > 0 {
				// write remainder
				if  < len(.URL) {
					.WriteString(.URL[:])
				}
				.URL = .String()
			}
		}
	}

	// Parsing request URL
	,  := url.Parse(.URL)
	if  != nil {
		return 
	}

	// If Request.URL is relative path then added c.HostURL into
	// the request URL otherwise Request.URL will be used as-is
	if !.IsAbs() {
		.URL = .String()
		if len(.URL) > 0 && .URL[0] != '/' {
			.URL = "/" + .URL
		}

		// TODO: change to use c.BaseURL only in v3.0.0
		 := .BaseURL
		if len() == 0 {
			 = .HostURL
		}
		,  = url.Parse( + .URL)
		if  != nil {
			return 
		}
	}

	// GH #407 && #318
	if .Scheme == "" && len(.scheme) > 0 {
		.Scheme = .scheme
	}

	// Adding Query Param
	if len(.QueryParam)+len(.QueryParam) > 0 {
		for ,  := range .QueryParam {
			// skip query parameter if it was set in request
			if ,  := .QueryParam[];  {
				continue
			}

			.QueryParam[] = [:]
		}

		// GitHub #123 Preserve query string order partially.
		// Since not feasible in `SetQuery*` resty methods, because
		// standard package `url.Encode(...)` sorts the query params
		// alphabetically
		if len(.QueryParam) > 0 {
			if IsStringEmpty(.RawQuery) {
				.RawQuery = .QueryParam.Encode()
			} else {
				.RawQuery = .RawQuery + "&" + .QueryParam.Encode()
			}
		}
	}

	.URL = .String()

	return nil
}

func parseRequestHeader( *Client,  *Request) error {
	for ,  := range .Header {
		if ,  := .Header[];  {
			continue
		}
		.Header[] = [:]
	}

	if IsStringEmpty(.Header.Get(hdrUserAgentKey)) {
		.Header.Set(hdrUserAgentKey, hdrUserAgentValue)
	}

	if  := .Header.Get(hdrContentTypeKey); IsStringEmpty(.Header.Get(hdrAcceptKey)) && !IsStringEmpty() && (IsJSONType() || IsXMLType()) {
		.Header.Set(hdrAcceptKey, .Header.Get(hdrContentTypeKey))
	}

	return nil
}

func parseRequestBody( *Client,  *Request) error {
	if isPayloadSupported(.Method, .AllowGetMethodPayload) {
		switch {
		case .isMultiPart: // Handling Multipart
			if  := handleMultipart(, );  != nil {
				return 
			}
		case len(.FormData) > 0 || len(.FormData) > 0: // Handling Form Data
			handleFormData(, )
		case .Body != nil: // Handling Request body
			handleContentType(, )

			if  := handleRequestBody(, );  != nil {
				return 
			}
		}
	}

	// by default resty won't set content length, you can if you want to :)
	if .setContentLength || .setContentLength {
		if .bodyBuf == nil {
			.Header.Set(hdrContentLengthKey, "0")
		} else {
			.Header.Set(hdrContentLengthKey, strconv.Itoa(.bodyBuf.Len()))
		}
	}

	return nil
}

func createHTTPRequest( *Client,  *Request) ( error) {
	if .bodyBuf == nil {
		if ,  := .Body.(io.Reader);  && isPayloadSupported(.Method, .AllowGetMethodPayload) {
			.RawRequest,  = http.NewRequest(.Method, .URL, )
		} else if .setContentLength || .setContentLength {
			.RawRequest,  = http.NewRequest(.Method, .URL, http.NoBody)
		} else {
			.RawRequest,  = http.NewRequest(.Method, .URL, nil)
		}
	} else {
		// fix data race: must deep copy.
		 := bytes.NewBuffer(append([]byte{}, .bodyBuf.Bytes()...))
		.RawRequest,  = http.NewRequest(.Method, .URL, )
	}

	if  != nil {
		return
	}

	// Assign close connection option
	.RawRequest.Close = .closeConnection

	// Add headers into http request
	.RawRequest.Header = .Header

	// Add cookies from client instance into http request
	for ,  := range .Cookies {
		.RawRequest.AddCookie()
	}

	// Add cookies from request instance into http request
	for ,  := range .Cookies {
		.RawRequest.AddCookie()
	}

	// Enable trace
	if .trace || .trace {
		.clientTrace = &clientTrace{}
		.ctx = .clientTrace.createContext(.Context())
	}

	// Use context if it was specified
	if .ctx != nil {
		.RawRequest = .RawRequest.WithContext(.ctx)
	}

	,  := getBodyCopy()
	if  != nil {
		return 
	}

	// assign get body func for the underlying raw request instance
	.RawRequest.GetBody = func() (io.ReadCloser, error) {
		if  != nil {
			return io.NopCloser(bytes.NewReader(.Bytes())), nil
		}
		return nil, nil
	}

	return
}

func addCredentials( *Client,  *Request) error {
	var  bool
	// Basic Auth
	if .UserInfo != nil { // takes precedence
		.RawRequest.SetBasicAuth(.UserInfo.Username, .UserInfo.Password)
		 = true
	} else if .UserInfo != nil {
		.RawRequest.SetBasicAuth(.UserInfo.Username, .UserInfo.Password)
		 = true
	}

	if !.DisableWarn {
		if  && !strings.HasPrefix(.URL, "https") {
			.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS")
		}
	}

	// Set the Authorization Header Scheme
	var  string
	if !IsStringEmpty(.AuthScheme) {
		 = .AuthScheme
	} else if !IsStringEmpty(.AuthScheme) {
		 = .AuthScheme
	} else {
		 = "Bearer"
	}

	// Build the Token Auth header
	if !IsStringEmpty(.Token) { // takes precedence
		.RawRequest.Header.Set(.HeaderAuthorizationKey, +" "+.Token)
	} else if !IsStringEmpty(.Token) {
		.RawRequest.Header.Set(.HeaderAuthorizationKey, +" "+.Token)
	}

	return nil
}

func requestLogger( *Client,  *Request) error {
	if .Debug {
		 := .RawRequest
		 := copyHeaders(.Header)
		if .GetClient().Jar != nil {
			for ,  := range .GetClient().Jar.Cookies(.RawRequest.URL) {
				 := fmt.Sprintf("%s=%s", .Name, .Value)
				if  := .Get("Cookie");  != "" {
					.Set("Cookie", +"; "+)
				} else {
					.Set("Cookie", )
				}
			}
		}
		 := &RequestLog{Header: , Body: .fmtBodyString(.debugBodySizeLimit)}
		if .requestLog != nil {
			if  := .requestLog();  != nil {
				return 
			}
		}

		 := "\n==============================================================================\n" +
			"~~~ REQUEST ~~~\n" +
			fmt.Sprintf("%s  %s  %s\n", .Method, .URL.RequestURI(), .Proto) +
			fmt.Sprintf("HOST   : %s\n", .URL.Host) +
			fmt.Sprintf("HEADERS:\n%s\n", composeHeaders(, , .Header)) +
			fmt.Sprintf("BODY   :\n%v\n", .Body) +
			"------------------------------------------------------------------------------\n"

		.initValuesMap()
		.values[debugRequestLogKey] = 
	}

	return nil
}

//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// Response Middleware(s)
//_______________________________________________________________________

func responseLogger( *Client,  *Response) error {
	if .Request.Debug {
		 := &ResponseLog{Header: copyHeaders(.Header()), Body: .fmtBodyString(.debugBodySizeLimit)}
		if .responseLog != nil {
			if  := .responseLog();  != nil {
				return 
			}
		}

		 := .Request.values[debugRequestLogKey].(string)
		 += "~~~ RESPONSE ~~~\n" +
			fmt.Sprintf("STATUS       : %s\n", .Status()) +
			fmt.Sprintf("PROTO        : %s\n", .RawResponse.Proto) +
			fmt.Sprintf("RECEIVED AT  : %v\n", .ReceivedAt().Format(time.RFC3339Nano)) +
			fmt.Sprintf("TIME DURATION: %v\n", .Time()) +
			"HEADERS      :\n" +
			composeHeaders(, .Request, .Header) + "\n"
		if .Request.isSaveResponse {
			 += "BODY         :\n***** RESPONSE WRITTEN INTO FILE *****\n"
		} else {
			 += fmt.Sprintf("BODY         :\n%v\n", .Body)
		}
		 += "==============================================================================\n"

		.Request.log.Debugf("%s", )
	}

	return nil
}

func parseResponseBody( *Client,  *Response) ( error) {
	if .StatusCode() == http.StatusNoContent {
		.Request.Error = nil
		return
	}
	// Handles only JSON or XML content type
	 := firstNonEmpty(.Request.forceContentType, .Header().Get(hdrContentTypeKey), .Request.fallbackContentType)
	if IsJSONType() || IsXMLType() {
		// HTTP status code > 199 and < 300, considered as Result
		if .IsSuccess() {
			.Request.Error = nil
			if .Request.Result != nil {
				 = Unmarshalc(, , .body, .Request.Result)
				return
			}
		}

		// HTTP status code > 399, considered as Error
		if .IsError() {
			// global error interface
			if .Request.Error == nil && .Error != nil {
				.Request.Error = reflect.New(.Error).Interface()
			}

			if .Request.Error != nil {
				 := Unmarshalc(, , .body, .Request.Error)
				if  != nil {
					.log.Warnf("Cannot unmarshal response body: %s", )
				}
			}
		}
	}

	return
}

func handleMultipart( *Client,  *Request) error {
	.bodyBuf = acquireBuffer()
	 := multipart.NewWriter(.bodyBuf)

	for ,  := range .FormData {
		for ,  := range  {
			if  := .WriteField(, );  != nil {
				return 
			}
		}
	}

	for ,  := range .FormData {
		for ,  := range  {
			if strings.HasPrefix(, "@") { // file
				if  := addFile(, [1:], );  != nil {
					return 
				}
			} else { // form value
				if  := .WriteField(, );  != nil {
					return 
				}
			}
		}
	}

	// #21 - adding io.Reader support
	for ,  := range .multipartFiles {
		if  := addFileReader(, );  != nil {
			return 
		}
	}

	// GitHub #130 adding multipart field support with content type
	for ,  := range .multipartFields {
		if  := addMultipartFormField(, );  != nil {
			return 
		}
	}

	.Header.Set(hdrContentTypeKey, .FormDataContentType())
	return .Close()
}

func handleFormData( *Client,  *Request) {
	for ,  := range .FormData {
		if ,  := .FormData[];  {
			continue
		}
		.FormData[] = [:]
	}

	.bodyBuf = acquireBuffer()
	.bodyBuf.WriteString(.FormData.Encode())
	.Header.Set(hdrContentTypeKey, formContentType)
	.isFormData = true
}

func handleContentType( *Client,  *Request) {
	 := .Header.Get(hdrContentTypeKey)
	if IsStringEmpty() {
		 = DetectContentType(.Body)
		.Header.Set(hdrContentTypeKey, )
	}
}

func handleRequestBody( *Client,  *Request) error {
	var  []byte
	releaseBuffer(.bodyBuf)
	.bodyBuf = nil

	switch body := .Body.(type) {
	case io.Reader:
		if .setContentLength || .setContentLength { // keep backward compatibility
			.bodyBuf = acquireBuffer()
			if ,  := .bodyBuf.ReadFrom();  != nil {
				return 
			}
			.Body = nil
		} else {
			// Otherwise buffer less processing for `io.Reader`, sounds good.
			return nil
		}
	case []byte:
		 = 
	case string:
		 = []byte()
	default:
		 := .Header.Get(hdrContentTypeKey)
		 := kindOf(.Body)
		var  error
		if IsJSONType() && ( == reflect.Struct ||  == reflect.Map ||  == reflect.Slice) {
			.bodyBuf,  = jsonMarshal(, , .Body)
		} else if IsXMLType() && ( == reflect.Struct) {
			,  = .XMLMarshal(.Body)
		}
		if  != nil {
			return 
		}
	}

	if  == nil && .bodyBuf == nil {
		return errors.New("unsupported 'Body' type/value")
	}

	// []byte into Buffer
	if  != nil && .bodyBuf == nil {
		.bodyBuf = acquireBuffer()
		_, _ = .bodyBuf.Write()
	}

	return nil
}

func saveResponseIntoFile( *Client,  *Response) error {
	if .Request.isSaveResponse {
		 := ""

		if len(.outputDirectory) > 0 && !filepath.IsAbs(.Request.outputFile) {
			 += .outputDirectory + string(filepath.Separator)
		}

		 = filepath.Clean( + .Request.outputFile)
		if  := createDirectory(filepath.Dir());  != nil {
			return 
		}

		,  := os.Create()
		if  != nil {
			return 
		}
		defer closeq()

		// io.Copy reads maximum 32kb size, it is perfect for large file download too
		defer closeq(.RawResponse.Body)

		,  := io.Copy(, .RawResponse.Body)
		if  != nil {
			return 
		}

		.size = 
	}

	return nil
}

func getBodyCopy( *Request) (*bytes.Buffer, error) {
	// If r.bodyBuf present, return the copy
	if .bodyBuf != nil {
		 := acquireBuffer()
		if ,  := io.Copy(, bytes.NewReader(.bodyBuf.Bytes()));  != nil {
			// cannot use io.Copy(bodyCopy, r.bodyBuf) because io.Copy reset r.bodyBuf
			return nil, 
		}
		return , nil
	}

	// Maybe body is `io.Reader`.
	// Note: Resty user have to watchout for large body size of `io.Reader`
	if .RawRequest.Body != nil {
		,  := io.ReadAll(.RawRequest.Body)
		if  != nil {
			return nil, 
		}

		// Restore the Body
		closeq(.RawRequest.Body)
		.RawRequest.Body = io.NopCloser(bytes.NewBuffer())

		// Return the Body bytes
		return bytes.NewBuffer(), nil
	}
	return nil, nil
}