package manager

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"

	"chainweaver.org.cn/chainweaver/did/core"
	"chainweaver.org.cn/chainweaver/did/core/crypto"
	"chainweaver.org.cn/chainweaver/did/did-kms/kms"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/errorcode"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/config"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/db"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/middleware"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/svc"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/types"
	"chainweaver.org.cn/chainweaver/did/did-mgr-common-service/internal/utils"
	"chainweaver.org.cn/chainweaver/servicecommon/res/code"
	"google.golang.org/grpc/metadata"

	"github.com/zeromicro/go-zero/core/logx"
)

type KeyAddLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewKeyAddLogic(ctx context.Context, svcCtx *svc.ServiceContext) *KeyAddLogic {
	return &KeyAddLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *KeyAddLogic) KeyAdd(req *types.KeyPairReq) (resp *types.CommonResp, err error) {
	resp = &types.CommonResp{
		Code: types.SucceedCode,
		Msg:  types.SucceedMsg,
	}
	token := l.ctx.Value("claims").(*middleware.TokenUser)

	if req.Mnemonic != "" && len(strings.Split(req.Mnemonic, " ")) != 12 {
		err = fmt.Errorf("expect 12 words,mnemonic[%s]", req.Mnemonic)
		errorcode.RequestErrorParametersFormat.BuildResult(resp, err)
		l.Logger.Info("add key failed,err[%s]", err.Error())
		return resp, nil
	}
	priKey, err := base64.StdEncoding.DecodeString(req.PrivateKey)
	if err != nil {
		errorcode.ParseErrorParsePrivateKeyFromPEM.BuildResult(resp, err)
		logx.Errorf("private key [%s] Parse err[%s]", req.PrivateKey, err.Error())
		return resp, nil
	}

	// 检查秘钥对是否有效,并计算address
	privateKey, err := crypto.ParsePrivateKeyFromPEM(priKey)
	if err != nil {
		errorcode.ParseErrorParsePrivateKeyFromPEM.BuildResult(resp, err)
		logx.Errorf("private key [%s] Parse err[%s]", req.PrivateKey, err.Error())
		return resp, nil
	}

	pkPem, _ := privateKey.PublicKey().String()
	address, _ := utils.ComputeEVMAddressFromPKPEM([]string{pkPem})

	// 检查是否已经存在did中了,如果已经存在,则只需添加到本地数据库,并将keypair 添加到kms中
	req.PrivateKey = string(priKey)
	isExist, err := l.checkKeyIsExist(req, address[0], token.UserName)
	if err != nil {
		errorcode.MysqlErrorUpdate.BuildResult(resp, err)
		l.Logger.Info("check key isExist failed,err[%s]", err.Error())
		return resp, nil
	}
	if isExist {
		return resp, nil
	}

	//去运营端根据did address查询did 是否已经存在,不存在则添加到当前did中
	proxy := config.ConfigIns.Service.DidProxy
	targetUrl := config.ConfigIns.Service.Did + utils.DIdGetDocumentByAddressUrl
	docReq := &utils.DocumentGetByAddressRequest{Address: address[0]}
	qdoc, err := utils.GetDocument(targetUrl, proxy, docReq)
	if err != nil && !strings.Contains(err.Error(), "record not found") {
		errorcode.DocumentRequestError.BuildResult(resp, err)
		logx.Errorf("get [%s] document by address of remote did service, err[%s]", address[0], err.Error())
		return resp, nil
	}
	if qdoc != nil {
		errorcode.DidPrivateKeyUsageError.BuildResult(resp)
		logx.Errorf("address[%s] document[%s] already exist", address[0], qdoc.Id)
		return resp, nil
	}

	//1.kms 添加
	res, err := l.AddKeyPair(req)
	if err != nil {
		errorcode.KmsAddDataError.BuildResult(resp, err)
		l.Logger.Info("kms add key failed,err[%s]", err.Error())
		return resp, nil
	}

	plaintext, err := l.genAddKeyDocument(token.EnterpriseDid, []string{res.Data}, []string{pkPem})
	if err != nil {
		errorcode.DocumentGenerateError.BuildResult(resp, err)
		l.Logger.Info("document generate failed,err[%s]", err.Error())
		return resp, nil
	}

	doc := &core.Document{}
	err = json.Unmarshal(plaintext, doc)
	if err != nil {
		errorcode.MarshalOrUnMarshalError.BuildResult(resp, err)
		l.Logger.Info(" Unmarshal failed,err[%s]", err.Error())
		return resp, nil
	}

	plaintextStr := base64.StdEncoding.EncodeToString(plaintext)
	signResp, err := Sign(l.svcCtx.KmsClient, plaintextStr, res.Data, token.EnterpriseDid)
	if err != nil {
		errorcode.KmsSignRequestError.BuildResult(resp, err)
		l.Logger.Info("kms sign failed,err[%s]", err.Error())
		return resp, nil
	}

	verificationMetho := signResp.Data.VerificationMethod
	if signResp.Data.VerificationMethod == "" {
		for _, v := range doc.VerificationMethod {
			if v.Address == res.Data {
				verificationMetho = v.Id
				break
			}
		}
	}

	if verificationMetho == "" {
		err = fmt.Errorf("did does not have the expected address")
		errorcode.DocumentGenerateError.BuildResult(resp, err)
		l.Logger.Info(err.Error())
		return resp, nil
	}

	proof := &Proof{
		Created:            signResp.Data.Created,
		Type:               signResp.Data.Type,
		ProofValue:         signResp.Data.Signature,
		VerificationMethod: verificationMetho,
	}

	document, err := l.registerAddKeyDocument(token.EnterpriseDid, proof)
	if err != nil {
		errorcode.DocumentRegisterError.BuildResult(resp, err)
		l.Logger.Info(" register document failed,err[%s]", err.Error())
		return resp, nil
	}
	// 将did document信息保存至kms
	_, err = AddDidInfo(l.svcCtx.KmsClient, plaintext, document.Id)
	if err != nil {
		errorcode.KmsAddDataError.BuildResult(resp, err)
		l.Logger.Info("kms add did failed,err[%s]", err.Error())
		return resp, nil
	}

	//2.local save
	err = db.CreateTable(&db.KeyInfo{
		Address:   res.Data,
		AdminName: token.UserName,
		State:     1,
		Enable:    2,
		Remark:    req.Mnemonic,
	})
	if err != nil {
		errorcode.MysqlErrorInsertInfo.BuildResult(resp, err)
		l.Logger.Info("create key info failed,err[%s]", err.Error())
		return resp, nil
	}

	return resp, nil
}

func (l *KeyAddLogic) AddKeyPair(req *types.KeyPairReq) (*kms.AddKeyPairResponse, error) {

	in := &kms.AddKeyPairRequest{
		PrivateKey: req.PrivateKey,
		Mnemonic:   strings.Split(req.Mnemonic, " "),
	}

	res, err := l.svcCtx.KmsClient.AddKeyPair(l.ctx, in)
	if err != nil {
		return nil, err
	} else if code.CodeOk.NotEqualsInt32(res.Code) {
		return nil, fmt.Errorf(res.Msg)
	}

	return res, err
}

// DidUpdateGenerateReq 结构体,用于发送更新 DID 文档的请求
type DidUpdateGenerateReq struct {
	Did       string   `json:"did"`       // DID 文档
	PublicKey []string `json:"publicKey"` // 新的公钥列表
	Addresses []string `json:"addresses"` // 地址列表
}

// DidUpdateResp 结构体,用于返回更新 DID 文档的结果
type DidUpdateResp struct {
	Code int32          `json:"code"` // 状态码
	Msg  string         `json:"msg"`  // 消息
	Data *DidUpdateData `json:"data"` // 数据部分
}

// DidUpdateData 结构体,包含更新的 DID 文档信息和明文数据
type DidUpdateData struct {
	Plaintext Plaintext `json:"plaintext"` // 明文数据
	Did       string    `json:"did"`       // DID 文档
}

// Plaintext 结构体,包含类型和明文数据
type Plaintext struct {
	Type      int    `json:"type"`      // 类型
	Plaintext []byte `json:"plaintext"` // 明文数据
}

func (l *KeyAddLogic) genAddKeyDocument(enterpriseDid string, addresses, pubKeys []string) (plaintext []byte, err error) {
	// 企业DID已创建,添加地址
	updateResp := &DidUpdateGenerateReq{
		Did:       enterpriseDid,
		PublicKey: pubKeys,
		Addresses: addresses,
	}
	// DID绑定链地址
	// 公钥和DID发给DID服务,DID服务返回document,让用户对document签名
	param, _ := json.Marshal(updateResp)
	proxy := config.ConfigIns.Service.DidProxy
	targetUrl := config.ConfigIns.Service.Did + utils.DIdUpdateGenerateUrl + utils.DIdUpdateGenerateActionAdd
	respBz, internalErr := utils.DidServerReq(targetUrl, proxy, http.MethodPost, param)
	if internalErr != nil {
		l.Logger.Errorf("GetDocument HttpServerReq failed, internalErr=%s", internalErr.Error())
		return nil, internalErr
	}
	didResp := &DidUpdateResp{}
	internalErr = json.Unmarshal(respBz, didResp)
	if internalErr != nil {

		l.Logger.Infof("GetDocument didResp json.Unmarshal failed.err = %s", internalErr.Error())
		return nil, internalErr
	}
	if didResp.Code != types.SucceedCode {
		l.Logger.Infof("GetDocument didResp.Code != 200000.didResp = %v", *didResp)
		return nil, fmt.Errorf(didResp.Msg)
	}

	return didResp.Data.Plaintext.Plaintext, nil
}

// DidUpdateProof 结构体包含 DID 和证明列表,用于更新 DID 的证明
type DidUpdateProof struct {
	Did   string   `json:"did"`
	Proof []*Proof `json:"proof"`
}

func (l *KeyAddLogic) registerAddKeyDocument(enterpriseDid string, proof *Proof) (*core.Document, error) {
	didUpdateProof := &DidUpdateProof{
		Did: enterpriseDid,
	}
	didUpdateProof.Proof = append(didUpdateProof.Proof, proof)

	param, _ := json.Marshal(didUpdateProof)
	targetUrl := config.ConfigIns.Service.Did + utils.DIdUpdateUrl
	proxy := config.ConfigIns.Service.DidProxy
	// 发送更新企业did请求到did服务
	respBz, internalErr := utils.DidServerReq(targetUrl, proxy, http.MethodPost, param)
	if internalErr != nil {
		l.Logger.Errorf("RegisterDocument HttpServerReq failed, internalErr=%s", internalErr.Error())
		return nil, internalErr
	}

	didResp := &DIDRegisterResp{}
	internalErr = json.Unmarshal(respBz, didResp)
	if internalErr != nil {
		l.Logger.Infof("RegisterDocument didResp json.Unmarshal failed.err = %s", internalErr.Error())
		return nil, internalErr
	}
	if didResp.Code != types.SucceedCode {
		err := fmt.Errorf("RegisterDocument didResp.Code != 200000.didResp = %v", *didResp)
		l.Logger.Infof(err.Error())
		return nil, err
	}
	return didResp.Data.Document, nil
}

func Sign(client kms.Kms, plaintext, address, did string) (*kms.SignResponse, error) {

	in := &kms.SignRequest{
		Plaintext: plaintext,
		Address:   address,
	}

	context := context.Background()
	// 在请求头中设置自定义参数
	md := metadata.Pairs("did", did)
	ctx := metadata.NewOutgoingContext(context, md)

	res, err := client.Sign(ctx, in)
	if err != nil {
		return nil, err
	} else if code.CodeOk.NotEqualsInt32(res.Code) {
		return nil, fmt.Errorf(res.Msg)
	}

	return res, err
}

func (l *KeyAddLogic) checkKeyIsExist(req *types.KeyPairReq, address, userName string) (bool, error) {
	// 获得本地document
	did, err := db.FindDidInfo()
	if err != nil {
		return false, fmt.Errorf("query did failed,err[%s]", err.Error())
	}
	doc := &core.Document{}
	err = json.Unmarshal([]byte(did.DocumentPlaintext), doc)
	if err != nil {
		return false, fmt.Errorf("unmarshal document failed,err[%s]", err.Error())
	}

	isExist := false
	for _, vm := range doc.VerificationMethod {
		if vm.Address == address {
			isExist = true
			break
		}
	}
	if !isExist {
		return false, nil
	}

	//2.本地数据库检查是否已经存在
	keyInfo, err := db.FindKeyByAddress(address)
	if keyInfo != nil {
		return false, nil
	} else if err != nil {
		l.Logger.Errorf("FindKeyByAddress %s failed,err: %s", address, err.Error())
	}
	//1.kms 添加
	res, err := l.AddKeyPair(req)
	if err != nil {
		err = fmt.Errorf("add key to kms failed,err[%s]", err.Error())
		l.Logger.Info(err.Error())
		return false, err
	}

	//2.local save
	err = db.CreateTable(&db.KeyInfo{
		Address:   res.Data,
		AdminName: userName,
		State:     1,
		Enable:    1,
		Remark:    req.Mnemonic,
	})
	if err != nil {
		err = fmt.Errorf("insert key info failed,err[%s]", err.Error())
		l.Logger.Info("insert key info failed,err[%s]", err.Error())
		return false, err
	}

	return true, nil
}