package mixrepoimpl

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-kratos/kratos/v2/log"
	http2 "github.com/go-kratos/kratos/v2/transport/http"
	"io"
	"net/http"
	"net/url"
	"time"
	err2 "voucher/api/err"
	v1 "voucher/api/v1"
	"voucher/internal/biz/bo"
	"voucher/internal/biz/mixrepos"
	"voucher/internal/biz/vo"
	"voucher/internal/conf"
	"voucher/internal/pkg/cmb"
	"voucher/internal/pkg/helper"
	"voucher/internal/pkg/request"
)

type CmbMixRepoImpl struct {
	bc *conf.Bootstrap

	// 连接池复用（优化网络开销）
	options *request.Options
}

func NewCmbMixRepoImpl(bc *conf.Bootstrap) mixrepos.CmbMixRepo {

	h := http.Header{
		"Content-Type": []string{"application/json"},
	}
	hc := &http.Client{
		Timeout: 20 * time.Second,
		Transport: &http.Transport{
			MaxIdleConns:        100,              // 最大空闲连接数
			MaxIdleConnsPerHost: 20,               // 每个主机的最大空闲连接数
			IdleConnTimeout:     30 * time.Second, // 空闲连接超时时间
		},
	}

	return &CmbMixRepoImpl{
		bc:      bc,
		options: request.NewOptions(request.WithHeaders(h), request.WithHttpClient(hc)),
	}
}

func (c *CmbMixRepoImpl) recordBody(ctx context.Context) {

	httpRequest, ok := http2.RequestFromServerContext(ctx)
	if !ok {
		log.Errorf("read body not ok")
		return
	}

	bodyBytes, err := io.ReadAll(httpRequest.Body)
	if err != nil {
		log.Errorf("read body not ok, %v", err)
		return
	}

	log.Errorf("body %v", string(bodyBytes))
}

func (s *CmbMixRepoImpl) OrderVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbOrderRequest, error) {

	bizStr, err := s.Verify(ctx, req)
	if err != nil {
		return nil, err2.ErrorCmbVerifyFail(err.Error())
	}

	if len(bizStr) == 0 {
		s.recordBody(ctx)
		return nil, err2.ErrorCmbBizContentFail("业务参数获取异常，请检查参数是否正确传递")
	}

	var bizContent *v1.CmbOrderRequest
	if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	if err = bizContent.Validate(); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	return bizContent, nil
}

func (s *CmbMixRepoImpl) QueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryRequest, error) {

	bizStr, err := s.Verify(ctx, req)
	if err != nil {
		return nil, err2.ErrorCmbVerifyFail(err.Error())
	}

	if len(bizStr) == 0 {
		s.recordBody(ctx)
		return nil, err2.ErrorCmbBizContentFail("业务参数获取异常，请检查参数是否正确传递")
	}

	var bizContent *v1.CmbQueryRequest
	if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	if err = bizContent.Validate(); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	return bizContent, nil
}

func (s *CmbMixRepoImpl) ProductQueryVerify(ctx context.Context, req *v1.CmbRequest) (*v1.CmbQueryProductRequest, error) {

	bizStr, err := s.Verify(ctx, req)
	if err != nil {
		return nil, err
	}

	if len(bizStr) == 0 {
		s.recordBody(ctx)
		return nil, err2.ErrorCmbBizContentFail("业务参数获取异常，请检查参数是否正确传递")
	}

	var bizContent *v1.CmbQueryProductRequest
	if err = json.Unmarshal([]byte(bizStr), &bizContent); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	if err = bizContent.Validate(); err != nil {
		return nil, err2.ErrorCmbBizContentFail(err.Error())
	}

	return bizContent, nil
}

func (s *CmbMixRepoImpl) Verify(_ context.Context, req *v1.CmbRequest) (string, error) {

	str := cmb.SortStructStr(req)

	b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
	if err != nil {
		return "", err2.ErrorCmbVerifyFail(err.Error())
	}

	if !b {
		return "", err2.ErrorCmbVerifyFail("签名验证失败")
	}

	bizBytes, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: req.EncryptBody, PrivateKey: s.bc.Cmb.Sm2Prk})
	if err != nil {
		return "", err2.ErrorCmbBizContentDecryptFail(err.Error())
	}

	return string(bizBytes), nil
}

func (s *CmbMixRepoImpl) GetRequest(_ context.Context, reqBo *bo.CmbRequestBo) (*v1.CmbRequest, error) {

	encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
	if err != nil {
		return nil, err
	}

	req := &v1.CmbRequest{
		Mid:         s.bc.Cmb.Mid,
		Aid:         s.bc.Cmb.Aid,
		Date:        time.Now().Format("20060102150405"),
		Random:      string(cmb.RandomBytes(16)),
		KeyAlias:    s.bc.Cmb.KeyAlias,
		CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
		EncryptBody: encryptBody,
		Sign:        "",
	}

	str := fmt.Sprintf("%s?%s", reqBo.FuncName, cmb.SortStructStr(req))

	sign, err := cmb.SignBody(str, s.bc.Cmb.Sm2Prk)
	if err != nil {
		return nil, err
	}

	req.Sign = sign

	return req, nil
}

func (s *CmbMixRepoImpl) GetMockRequest(_ context.Context, bizContent string) (*v1.CmbRequest, error) {

	if len(s.bc.Cmb.Sm2Puk) == 0 {
		return nil, errors.New("mock sm2 puk is empty")
	}

	if len(s.bc.Cmb.CmbSm2Pik) == 0 {
		return nil, errors.New("mock cmb sm2 pik is empty")
	}

	encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.Sm2Puk, JsonParam: bizContent})
	if err != nil {
		return nil, err
	}

	req := &v1.CmbRequest{
		Mid:         s.bc.Cmb.Mid,
		Aid:         s.bc.Cmb.Aid,
		Date:        time.Now().Format("20060102150405"),
		Random:      string(cmb.RandomBytes(16)),
		KeyAlias:    s.bc.Cmb.KeyAlias,
		CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
		EncryptBody: encryptBody,
		Sign:        "",
	}

	sign, err := cmb.SignBody(cmb.SortStructStr(req), s.bc.Cmb.CmbSm2Pik)
	if err != nil {
		return nil, err
	}

	req.Sign = sign

	return req, nil
}

func (s *CmbMixRepoImpl) VerifyResponse(_ context.Context, req *v1.CmbReply) error {

	str := cmb.SortStructStr(req)

	b, err := cmb.VerifyBody(str, req.Sign, s.bc.Cmb.CmbSm2Puk)
	if err != nil {
		return err
	}

	if !b {
		return errors.New("签名验证失败")
	}

	return nil
}

func (s *CmbMixRepoImpl) GetResponse(_ context.Context, reqBo *bo.CmbResponseBo) (*v1.CmbReply, error) {

	reply := &v1.CmbReply{
		RespCode:    reqBo.RespCode,
		RespMsg:     reqBo.RespMsg,
		Date:        time.Now().Format("20060102150405"),
		KeyAlias:    s.bc.Cmb.KeyAlias,
		CmbKeyAlias: s.bc.Cmb.CmbKeyAlias,
		EncryptBody: "",
		Sign:        "",
	}

	if len(reqBo.BizContent) > 0 {
		encryptBody, err := cmb.EncryptBody(&cmb.Encrypts{SoaPubKey: s.bc.Cmb.CmbSm2Puk, JsonParam: reqBo.BizContent})
		if err != nil {
			return nil, err
		}
		reply.EncryptBody = encryptBody
	}

	sign, err := cmb.SignBody(cmb.SortStructStr(reply), s.bc.Cmb.Sm2Prk)
	if err != nil {
		return nil, err
	}

	reply.Sign = sign

	return reply, nil
}

func (s *CmbMixRepoImpl) Request(ctx context.Context, req *v1.CmbRequest, uri string) (*v1.CmbReply, error) {

	kvRows := helper.SortStructFieldsByKey(req)

	uv := url.Values{}

	for _, kv := range kvRows {
		uv.Set(kv.Key, fmt.Sprintf("%v", kv.Value))
	}

	h := http.Header{
		"Content-Type": []string{"application/x-www-form-urlencoded"},
	}

	r := uri + "?" + uv.Encode()

	_, bodyBytes, err := request.Post(ctx, r, nil, request.WithHeaders(h), request.WithTimeout(time.Second*20))
	if err != nil {
		//log.Errorf("请求掌上生活报错,url:%s,err:%v", r, err)
		return nil, fmt.Errorf("CMB请求失败:%v", err)
	}

	var response *v1.CmbReply
	if err = json.Unmarshal(bodyBytes, &response); err != nil {
		log.Errorf("请求掌上生活返回数据解析报错:%s,url:%s,bodyBytes:%s", err.Error(), r, string(bodyBytes))
		return nil, fmt.Errorf("CMB数据解析错误:%s", err.Error())
	}

	if response.RespCode != vo.CmbResponseStatusSuccess.GetValue() {
		//log.Errorf("请求掌上生活返回报错:msg:%s,url:%s,bodyBytes:%s", response.RespMsg, r, string(bodyBytes))
		return nil, fmt.Errorf("CMB请求返回错误:%s", response.RespMsg)
	}

	return response, nil
}

func (s *CmbMixRepoImpl) Decrypt(_ context.Context, encryptBody string) (string, error) {

	if len(s.bc.Cmb.CmbSm2Pik) == 0 {
		return "", errors.New("mock CmbSm2Pik is empty")
	}

	rs, err := cmb.DecryptBody(&cmb.Decrypts{EncryptBody: encryptBody, PrivateKey: s.bc.Cmb.CmbSm2Pik})
	if err != nil {
		return "", err
	}

	return string(rs), nil
}
