package requests

import (
	"errors"
	"fmt"
	"golib.gaore.com/GaoreGo/gaore-common-sdk-go/sdk/utils"
	"io"
	"math/cmplx"
	"reflect"
	"strconv"
	"time"
)

const (
	RPC    = "RPC"
	ROA    = "ROA"
	STREAM = "STREAM"

	HTTP  = "HTTP"
	HTTPS = "HTTPS"

	JSON = "JSON"
	XML  = "XML"

	DefaultHttpPort = "80"

	GET     = "GET"
	PUT     = "PUT"
	POST    = "POST"
	DELETE  = "DELETE"
	PATCH   = "PATCH"
	HEAD    = "HEAD"
	OPTIONS = "OPTIONS"

	Json     = "application/json"
	Xml      = "application/xml"
	Raw      = "application/octet-stream"
	Form     = "application/x-www-form-urlencoded"
	FormData = "multipart/form-data"

	Header   = "Header"
	Query    = "Query"
	Body     = "Body"
	BodyJson = "Json"
	Path     = "Path"

	TEST    = "TEST"
	PRE     = "PRE"
	RELEASE = "RELEASE"

	HeaderSeparator = "\n"
)

type Host struct {
	Default string
	Func    func(string) string
}

var debug utils.Debug

func init() {
	debug = utils.Init("request")
}

type AcsRequest interface {
	GetReadTimeout() time.Duration
	GetConnectTimeout() time.Duration
	SetReadTimeout(readTimeOut time.Duration)
	SetConnectTimeout(connectTimeOut time.Duration)
	SetHTTPSInsecure(isInsecure bool)
	GetHTTPSInsecure() *bool
	GetQueryParams() map[string]string
	GetFormParams() map[string]string
	GetMethod() string
	GetScheme() string
	GetDomain() Host
	SetDomain(host Host)
	GetActionName() string
	GetAcceptFormat() string
	GetAccept() string
	GetHeaders() map[string]string
	GetStyle() string
	InitWithApiInfo(domain Host, version, urlPath string)
	GetEnv() string
	SetEnv(string)

	BuildUrl() string
	BuildQueries() string

	SetScheme(scheme string)
	SetContent(content []byte)

	SetStringToSign(stringToSign string)
	GetStringToSign() string
	GetBodyReader() io.Reader

	AddHeaderParam(key, value string)
	addQueryParam(key, value string)
	addFormParam(key, value string)
	addJsonParam(string, any)
}

type baseRequest struct {
	Scheme         string
	Method         string
	Port           string
	Domain         Host
	From           string
	ReadTimeout    time.Duration
	ConnectTimeout time.Duration
	isInsecure     *bool
	Env            string

	AcceptFormat string
	actionName   string

	userAgent map[string]string
	product   string
	version   string

	QueryParams map[string]string
	Headers     map[string]string
	FormParams  map[string]string
	JsonParams  map[string]any
	Content     []byte

	queries      string
	stringToSign string
}

func (request *baseRequest) GetEnv() string {
	return request.Env
}

func (request *baseRequest) SetEnv(e string) {
	request.Env = e
}

func (request *baseRequest) GetStringToSign() string {
	return request.stringToSign
}

func (request *baseRequest) SetContent(content []byte) {
	request.Content = content
}

func (request *baseRequest) GetAcceptFormat() string {
	return request.AcceptFormat
}

func (request *baseRequest) GetAccept() string {
	switch request.GetAcceptFormat() {
	case JSON:
		return Json
	case XML:
		return Xml
	}
	return ""
}

func (request *baseRequest) GetHeaders() map[string]string {
	return request.Headers
}

func (request *baseRequest) GetActionName() string {
	return request.actionName
}

func (request *baseRequest) SetScheme(scheme string) {
	request.Scheme = scheme
}

func (request *baseRequest) SetDomain(host Host) {
	request.Domain = host
}

func (request *baseRequest) GetScheme() string {
	return request.Scheme
}

func (request *baseRequest) GetDomain() Host {
	return request.Domain
}

func (request *baseRequest) GetMethod() string {
	return request.Method
}

func (request *baseRequest) GetFormParams() map[string]string {
	return request.FormParams
}

func (request *baseRequest) GetQueryParams() map[string]string {
	return request.QueryParams
}

func (request *baseRequest) SetHTTPSInsecure(isInsecure bool) {
	request.isInsecure = &isInsecure
}

func (request *baseRequest) GetHTTPSInsecure() *bool {
	return request.isInsecure
}

func (request *baseRequest) GetReadTimeout() time.Duration {
	return request.ReadTimeout
}

func (request *baseRequest) GetConnectTimeout() time.Duration {
	return request.ConnectTimeout
}

func (request *baseRequest) SetReadTimeout(readTimeOut time.Duration) {
	request.ReadTimeout = readTimeOut
}

func (request *baseRequest) SetConnectTimeout(connectTimeOut time.Duration) {
	request.ConnectTimeout = connectTimeOut
}

func (request *baseRequest) SetStringToSign(stringToSign string) {
	request.stringToSign = stringToSign
}

func (request *baseRequest) AddHeaderParam(key, val string) {
	request.Headers[key] = val
}

func (request *baseRequest) addQueryParam(key, val string) {
	request.QueryParams[key] = val
}

func (request *baseRequest) addFormParam(key, val string) {
	request.FormParams[key] = val
}

func (request *baseRequest) addJsonParam(key string, val any) {
	request.JsonParams[key] = val
}

func defaultBaseRequest() (request *baseRequest) {
	request = &baseRequest{
		Scheme:       HTTP,
		AcceptFormat: JSON,
		Method:       GET,
		QueryParams:  make(map[string]string),
		Headers: map[string]string{
			"Gr-Sdk-Client":      "golang/1.14",
			"Gr-Sdk-Invoke-Type": "normal",
			"Accept-Encoding":    Json,
		},
		FormParams: make(map[string]string),
		JsonParams: make(map[string]any),
	}
	return
}

func InitParam(request AcsRequest) (err error) {
	reflectValue := reflect.ValueOf(request).Elem()
	err = flatRepeatedList(reflectValue, request, "")
	return nil
}

func flatRepeatedList(reflectValue reflect.Value, request AcsRequest, position string) (err error) {
	reflectType := reflectValue.Type()
	for i := 0; i < reflectType.NumField(); i++ {
		field := reflectType.Field(i)
		name, isContiansNameTag := field.Tag.Lookup("field")

		fieldPosition := position
		if fieldPosition == "" {
			fieldPosition, _ = field.Tag.Lookup("position")
		}

		fieldDefault, _ := field.Tag.Lookup("default")
		debug("%s %s %s", name, fieldPosition, fieldDefault)
		if isContiansNameTag {
			var value string
			switch field.Type.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				value = strconv.FormatInt(reflectValue.Field(i).Int(), 10)
			case reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				value = strconv.FormatUint(reflectValue.Field(i).Uint(), 10)
			case reflect.Float32, reflect.Float64:
				value = strconv.FormatFloat(reflectValue.Field(i).Float(), 'E', -1, 64)
			case reflect.Bool:
				value = strconv.FormatBool(reflectValue.Field(i).Bool())
			case reflect.Complex64, reflect.Complex128:
				value = fmt.Sprint(cmplx.Sqrt(reflectValue.Field(i).Complex()))
			default:
				value = reflectValue.Field(i).String()
			}

			if len(value) == 0 {
				value = fieldDefault
			}

			if value == "0" && fieldDefault != "" && fieldDefault != "0" {
				value = fieldDefault
			}

			err = addParam(request, fieldPosition, name, value, reflectValue.Field(i).Interface())
		}
	}

	return
}

func addParam(request AcsRequest, position, key, value string, vAny any) (err error) {
	if len(value) > 0 {
		switch position {
		case Header:
			request.AddHeaderParam(key, value)
		case Query:
			request.addQueryParam(key, value)
		case Body:
			request.addFormParam(key, value)
		case BodyJson:
			request.addJsonParam(key, vAny)
		default:
			errmsg := fmt.Sprintf("unsupport positions add param `%s`", position)
			err = errors.New(errmsg)
		}
	}
	return
}