package cloudwatch

import (
	"bytes"
	"compress/gzip"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/private/protocol/query/queryutil"
	"net/url"
)

const (
	// putMetricDataByteRequestSizeLimit represents the maximum size of a CloudWatch PutMetricData request in bytes
	// By default, this is 40 KB (40960 bytes per error returned from CloudWatch)
	// See: https://w.amazon.com/index.php/MonitoringTeam/CloudWatchFrontend/CloudWatchFrontEnd/PutMetricData
	putMetricDataByteRequestSizeLimit = 40960
	// requestTooLargeErrorMsg represents the message of an error when the request size of a call is too large
	requestTooLargeErrorMsg = "request size too large"
	// ErrCodeRequestSize represents the error code when a request size is too large
	ErrCodeRequestSize = "RequestSizeError"
)

var requestSizeError = awserr.New(ErrCodeRequestSize, requestTooLargeErrorMsg, nil)

// buildPostGZip construct a gzip'd post request
func buildPostGZip(r *request.Request) {
	body := url.Values{
		"Action":  {r.Operation.Name},
		"Version": {r.ClientInfo.APIVersion},
	}

	if err := queryutil.Parse(body, r.Params, false); err != nil {
		r.Error = awserr.New(request.ErrCodeSerialization, "failed encoding Query request", err)
		return
	}
	// Clear params since they are no longer needed and have been applied to the body already
	r.Params = nil
	r.HTTPRequest.Method = "POST"
	r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
	r.HTTPRequest.Header.Set("Content-Encoding", "gzip")

	// Construct a byte buffer and gzip writer
	var w bytes.Buffer
	gzipW := gzip.NewWriter(&w)

	// GZip the body
	_, err := gzipW.Write([]byte(body.Encode()))
	if err != nil {
		r.Error = awserr.New(request.ErrCodeSerialization, "failed encoding gzip", err)
		return
	}
	err = gzipW.Close()
	if err != nil {
		r.Error = awserr.New(request.ErrCodeSerialization, "failed closing gzip writer", err)
		return
	}

	// Check the size of the request to determine whether the client should further split the request
	// Verified len(w.Bytes()) returns the same size for the request as the CloudWatch error message when the request is
	// too large (i.e. CloudWatch uses the request body size for the comparison).
	if len(w.Bytes()) > putMetricDataByteRequestSizeLimit {
		r.Error = requestSizeError
		return
	}
	r.SetBufferBody(w.Bytes())
}
