package kernel

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	request2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/request"
	response2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/response"

	"github.com/ArtisanCloud/PowerLibs/v3/http/contract"
	"github.com/ArtisanCloud/PowerLibs/v3/http/helper"
	contract2 "github.com/ArtisanCloud/PowerLibs/v3/logger/contract"
	"github.com/ArtisanCloud/PowerLibs/v3/object"
	"github.com/ArtisanCloud/PowerLibs/v3/os"
	"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power"
	"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/support"
)

type BaseClient struct {
	HttpHelper *helper.RequestHelper

	BaseURI  string
	QueryRaw bool

	*support.ResponseCastable

	Signer *support.SHA256WithRSASigner

	App   ApplicationInterface
	Token *AccessToken

	GetMiddlewareOfAccessToken        contract.RequestMiddleware
	GetMiddlewareOfLog                func(logger contract2.LoggerInterface) contract.RequestMiddleware
	GetMiddlewareOfRefreshAccessToken func(retry int) contract.RequestMiddleware
	Logger                            contract2.LoggerInterface
}

type UploadForm struct {
	FileName string
	Contents []*UploadContent
}
type UploadContent struct {
	Name  string
	Value interface{}
}

func NewBaseClient(app ApplicationInterface, token *AccessToken) (*BaseClient, error) {
	config := app.GetConfig()
	baseURI := config.GetString("http.base_uri", "/")
	proxyURI := config.GetString("http.proxy_uri", "")
	timeout := config.GetFloat64("http.timeout", 5)
	objTransport := config.Get("http.transport", nil)
	var transport http.RoundTripper = nil
	if objTransport != nil {
		if t, ok := objTransport.(http.RoundTripper); ok {
			transport = t
		}
	}
	if token == nil {
		token = app.GetAccessToken()
	}

	h, err := helper.NewRequestHelper(&helper.Config{
		BaseUrl: baseURI,
		ClientConfig: &contract.ClientConfig{
			Timeout:   time.Duration(timeout * float64(time.Second)),
			ProxyURI:  proxyURI,
			Transport: transport,
		},
	})
	if err != nil {
		return nil, err
	}
	if proxyURL := config.GetString("http.proxy", ""); proxyURL != "" {
		h.GetClient()
	}
	client := &BaseClient{
		HttpHelper: h,
		App:        app,
		Token:      token,
	}
	client.Logger = client.App.GetComponent("Logger").(contract2.LoggerInterface)

	// to be setup middleware here
	client.OverrideGetMiddlewares()
	client.RegisterHttpMiddlewares()

	mchID := config.GetString("mch_id", "")
	serialNO := config.GetString("serial_no", "")
	keyPath := config.GetString("key_path", "")
	if mchID != "" && serialNO != "" && keyPath != "" {
		client.Signer = &support.SHA256WithRSASigner{
			MchID:               mchID,
			CertificateSerialNo: serialNO,
			PrivateKeyPath:      keyPath,
		}
	}

	return client, nil
}

func (client *BaseClient) HttpGet(ctx context.Context, url string, query *object.StringMap, outHeader interface{}, outBody interface{}) (interface{}, error) {
	return client.Request(
		ctx,
		url,
		http.MethodGet,
		&object.HashMap{
			"query": query,
		},
		false,
		outHeader,
		outBody,
	)
}

func (client *BaseClient) HttpPost(ctx context.Context, url string, data interface{}, outHeader interface{}, outBody interface{}) (interface{}, error) {
	return client.Request(
		ctx,
		url,
		http.MethodPost,
		&object.HashMap{
			"form_params": data,
		},
		false,
		outHeader,
		outBody,
	)
}

func (client *BaseClient) HttpPostJson(ctx context.Context, url string, data interface{}, query *object.StringMap, outHeader interface{}, outBody interface{}) (interface{}, error) {
	return client.Request(
		ctx,
		url,
		http.MethodPost,
		&object.HashMap{
			"query":       query,
			"form_params": data,
		},
		false,
		outHeader,
		outBody,
	)
}

func (client *BaseClient) HttpPostJsonByEncode(ctx context.Context, url string, data interface{}, query *object.StringMap, outHeader interface{}, outBody interface{}) (interface{}, error) {
	return client.RequestByEncodedData(
		ctx,
		url,
		http.MethodPost,
		&object.HashMap{
			"query":       query,
			"form_params": data,
		},
		false,
		outHeader,
		outBody,
	)
}

func (client *BaseClient) HttpUpload(ctx context.Context, url string, files *object.HashMap, form *UploadForm, query interface{}, outHeader interface{}, outBody interface{}) (interface{}, error) {
	// http client request
	df := client.HttpHelper.Df().WithContext(ctx).Uri(url).Method(http.MethodPost)

	var mems map[string]io.Reader
	// 遍历表单的数据
	if form != nil {
		mems = make(map[string]io.Reader)
		for _, content := range form.Contents {
			value, err := os.ConvertFileHandleToReader(content.Value)
			if err != nil {
				return nil, err
			}
			mems[content.Name] = value
		}
	}

	headerValue := ctx.Value("headerKV")
	headerKV := &object.StringMap{}
	if headerValue != nil {
		headerKV = headerValue.(*object.StringMap)
	}

	df.Multipart(func(multipart contract.MultipartDfInterface) {
		// 遍历文件目录
		if files != nil {
			for name, path := range *files {
				multipart.FileByPath(name, path.(string))
			}
		}
		for k, v := range *headerKV {
			multipart.FieldValue(k, v)
		}

		for k, v := range mems {
			multipart.FileMem(form.FileName, k, v)
		}
	})

	// set query params
	if query != nil {
		queries := query.(*object.StringMap)
		if queries != nil {
			for k, v := range *queries {
				df.Query(k, v)
			}
		}
	}

	// set debug mode
	config := client.App.GetConfig()
	// 微信如果需要传debug模式
	debug := config.GetBool("debug", false)
	if debug {
		df.Query("debug", "1")
	}

	response, err := df.Request()
	if err != nil {
		return response, err
	}

	// decode response body to outBody
	if outBody != nil {
		err = client.HttpHelper.ParseResponseBodyContent(response, outBody)
		if err != nil {
			return nil, err
		}
	}

	return response, err
}

func (client *BaseClient) Request(ctx context.Context, url string, method string, options *object.HashMap,
	returnRaw bool, outHeader interface{}, outBody interface{},
) (*http.Response, error) {
	// http client request
	client.QueryRaw = returnRaw
	df := client.HttpHelper.Df().WithContext(ctx).Uri(url).Method(method)

	// 检查是否需要有请求参数配置
	if options != nil {
		// set query key values
		if (*options)["query"] != nil {
			queries := (*options)["query"].(*object.StringMap)
			if queries != nil {
				for k, v := range *queries {
					df.Query(k, v)
				}
			}
		}

		config := client.App.GetConfig()
		// 微信如果需要传debug模式
		debug := config.GetBool("debug", false)
		if debug {
			df.Query("debug", "1")
		}

		// set body json
		if (*options)["form_params"] != nil {
			if str, ok := (*options)["form_params"].(string); ok {
				r := bytes.NewBufferString(str)
				df.Body(r)
			} else {
				df.Json((*options)["form_params"])
			}
		}
	}

	ctxQuery := ctx.Value("query")
	if ctxQuery != nil {
		queries := ctxQuery.(*object.StringMap)
		if queries != nil {
			for k, v := range *queries {
				df.Query(k, v)
			}
		}
	}

	response, err := df.Request()
	if err != nil {
		return response, err
	}

	// decode response body to outBody
	if outBody != nil {
		err = client.HttpHelper.ParseResponseBodyContent(response, outBody)
		if err != nil {
			return nil, err
		}
	}

	// decode response header to outHeader
	if outHeader != nil {
		strHeader, err := object.JsonEncode(response.Header)
		if err != nil {
			return nil, err
		}
		err = object.JsonDecode([]byte(strHeader), outHeader)
		if err != nil {
			return nil, err
		}
	}

	return response, err
}

func (client *BaseClient) RequestByEncodedData(ctx context.Context, url string, method string, options *object.HashMap,
	returnRaw bool, outHeader interface{}, outBody interface{},
) (*http.Response, error) {
	// http client request
	client.QueryRaw = returnRaw
	df := client.HttpHelper.Df().WithContext(ctx).Uri(url).Method(method)

	// 检查是否需要有请求参数配置
	if options != nil {
		// set query key values
		if (*options)["query"] != nil {
			queries := (*options)["query"].(*object.StringMap)
			if queries != nil {
				for k, v := range *queries {
					df.Query(k, v)
				}
			}
		}

		config := client.App.GetConfig()
		// 微信如果需要传debug模式
		debug := config.GetBool("debug", false)
		if debug {
			df.Query("debug", "1")
		}

		// set body json
		if (*options)["form_params"] != nil {
			body := &power.JsonEncoder{
				Data: (*options)["form_params"],
			}
			df.Any(body)
		}
	}

	ctxQuery := ctx.Value("query")
	if ctxQuery != nil {
		queries := ctxQuery.(*object.StringMap)
		if queries != nil {
			for k, v := range *queries {
				df.Query(k, v)
			}
		}
	}

	response, err := df.Request()
	if err != nil {
		return response, err
	}

	// decode response body to outBody
	if outBody != nil {
		err = client.HttpHelper.ParseResponseBodyContent(response, outBody)
		if err != nil {
			return nil, err
		}
	}

	// decode response header to outHeader
	if outHeader != nil {
		strHeader, err := object.JsonEncode(response.Header)
		if err != nil {
			return nil, err
		}
		err = object.JsonDecode([]byte(strHeader), outHeader)
		if err != nil {
			return nil, err
		}
	}

	return response, err
}

func (client *BaseClient) RequestRaw(ctx context.Context, url string, method string, options *object.HashMap, outHeader interface{}, outBody interface{}) (*http.Response, error) {
	return client.Request(ctx, url, method, options, true, outHeader, outBody)
}

func (client *BaseClient) RegisterHttpMiddlewares() {
	// access token
	accessTokenMiddleware := client.GetMiddlewareOfAccessToken
	// log
	logMiddleware := client.GetMiddlewareOfLog

	// check invalid access token
	checkAccessTokenMiddleware := client.GetMiddlewareOfRefreshAccessToken

	config := client.App.GetConfig()
	client.HttpHelper.WithMiddleware(
		accessTokenMiddleware,
		logMiddleware(client.Logger),
		checkAccessTokenMiddleware(3),
		helper.HttpDebugMiddleware(config.GetBool("http_debug", false)),
	)
}

// ----------------------------------------------------------------------

func (client *BaseClient) OverrideGetMiddlewares() {
	client.OverrideGetMiddlewareOfLog()
	client.OverrideGetMiddlewareOfAccessToken()
	client.OverrideGetMiddlewareOfRefreshAccessToken()
}

func (client *BaseClient) OverrideGetMiddlewareOfAccessToken() {
	client.GetMiddlewareOfAccessToken = func(handle contract.RequestHandle) contract.RequestHandle {
		return func(request *http.Request) (response *http.Response, err error) {
			// 前置中间件
			// fmt.Println("获取access token, 在请求前执行")

			accessToken := client.App.GetAccessToken()

			if accessToken != nil {
				config := client.App.GetContainer().Config
				request, err = accessToken.ApplyToRequest(request, config)
			}

			if err != nil {
				return nil, err
			}

			response, err = handle(request)
			if err != nil {
				return response, err
			}

			// handle 执行之后就可以操作 response 和 err
			// 后置中间件
			// fmt.Println("获取access token, 在请求后执行")
			return
		}
	}
}

func (client *BaseClient) OverrideGetMiddlewareOfLog() {
	client.GetMiddlewareOfLog = func(logger contract2.LoggerInterface) contract.RequestMiddleware {
		return func(handle contract.RequestHandle) contract.RequestHandle {
			return func(request *http.Request) (response *http.Response, err error) {
				config := client.App.GetConfig()
				defer func() {
					debug := config.GetBool("http_debug", false)
					if debug {
						logger := logger.WithContext(request.Context())
						// 此处请求前后日志根据 log 配置中的 level 判断是否打印
						request2.LogRequest(logger, request)
						if response != nil {
							response2.LogResponse(logger, response)
						}
					}
				}()
				response, err = handle(request)
				if err != nil {
					return response, err
				}
				return
			}
		}
	}
}

func (client *BaseClient) OverrideGetMiddlewareOfRefreshAccessToken() {
	client.GetMiddlewareOfRefreshAccessToken = func(retry int) contract.RequestMiddleware {
		return contract.RequestMiddleware(func(handle contract.RequestHandle) contract.RequestHandle {
			return func(request *http.Request) (response *http.Response, err error) {
				// 前置中间件
				// fmt.Println("检查微信返回错误，token是否失效，执行前访问")

				response, err = handle(request)
				// handle 执行之后就可以操作 response 和 err
				if err != nil {
					return response, err
				}

				if response.StatusCode != http.StatusOK {
					return response, fmt.Errorf("http response code:%d", response.StatusCode)
				}

				rs, err := client.CheckTokenNeedRefresh(request, response, retry)
				if err != nil {
					return rs, err
				} else if rs != nil {
					return rs, nil
				}

				// 后置中间件
				// fmt.Println("检查微信返回错误，token是否失效，, 在请求后执行")
				return
			}
		})
	}
}

func (client *BaseClient) CheckTokenNeedRefresh(req *http.Request, rs *http.Response, retry int) (*http.Response, error) {
	ctx := req.Context()

	// 如何微信返回的是二进制数据流，那么就无须判断返回的err code是否正常
	if client.QueryRaw {
		if !strings.Contains(rs.Header.Get("Content-Type"), "application/json") {
			return rs, nil
		}
	}

	mapResponse := &object.HashMap{}
	err := client.HttpHelper.ParseResponseBodyToMap(rs, mapResponse)
	if err != nil {
		return nil, err
	}

	errCode := 0
	if (*mapResponse)["errcode"] != nil {
		switch (*mapResponse)["errcode"].(type) {
		case float64:
			errCode = int((*mapResponse)["errcode"].(float64))
		case int:
			errCode = (*mapResponse)["errcode"].(int)
		case string:
			errCode, err = strconv.Atoi((*mapResponse)["errcode"].(string))
		default:

		}

		conditions := &object.HashMap{
			"code": errCode,
		}
		if retry > 0 && client.RetryDecider(conditions) {
			client.Token.Refresh(ctx)

			// clone 一个request
			client.Logger.WithContext(ctx).InfoF("refresh token, retry:%d", retry)
			token, err := client.Token.GetToken(ctx, false)
			if err != nil {
				return nil, err
			}
			q := req.URL.Query()
			q.Set(client.Token.TokenKey, token.AccessToken)
			req.URL.RawQuery = q.Encode()
			req2 := req.Clone(ctx)
			if req.Body != nil {
				// 缓存请求body
				reqData, err := io.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}

				// 给两个request复制缓存下来的body数据
				req.Body = io.NopCloser(bytes.NewBuffer(reqData))
				req2.Body = io.NopCloser(bytes.NewReader(reqData))
			}

			res2, err := client.HttpHelper.GetClient().DoRequest(req2)
			if err != nil {
				return nil, err
			}

			return res2, err
		}
	}

	return rs, nil
}

func (client *BaseClient) RetryDecider(conditions *object.HashMap) bool {
	code := (*conditions)["code"].(int)
	if code == 40001 || code == 40014 || code == 42001 {
		return true
	}
	return false
}
