/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
/*
Notice: This file has been modified for Hyperledger Fabric SDK Go usage.
Please review third_party pinning scripts and patches for more details.
*/

package msp

import (
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"encoding/hex"

	"encoding/pem"
	"fmt"
	"sync"
	"time"

	"github.com/golang/protobuf/proto"
	"github.com/hyperledger/fabric-protos-go/msp"
	fbccsp "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/bccsp"
	cspx509 "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/bccsp/x509"
	bccsp "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/sdkpatch/cryptosuitebridge"
	logging "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/sdkpatch/logbridge"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/core"
	"github.com/pkg/errors"
)

var mspIdentityLogger = logging.MustGetLogger("msp.identity")

type identity struct {
	// id contains the identifier (MSPID and identity identifier) for this instance
	id *IdentityIdentifier

	// cert contains the x.509 certificate that signs the public key of this instance
	cert *x509.Certificate

	// this is the public key of this instance
	pk core.Key

	// reference to the MSP that "owns" this identity
	msp *bccspmsp

	// validationMutex is used to synchronise memory operation
	// over validated and validationErr
	validationMutex sync.Mutex

	// validated is true when the validateIdentity function
	// has been called on this instance
	validated bool

	// validationErr contains the validation error for this
	// instance. It can be read if validated is true
	validationErr error
}

func newIdentity(cert *x509.Certificate, pk core.Key, msp *bccspmsp) (Identity, error) {
	if mspIdentityLogger.IsEnabledFor(logging.DEBUG) {
		mspIdentityLogger.Debugf("Creating identity instance for cert %s", certToPEM(cert))
	}

	// Sanitize first the certificate
	cert, err := msp.sanitizeCert(cert)
	if err != nil {
		return nil, err
	}

	// Compute identity identifier

	// Use the hash of the identity's certificate as id in the IdentityIdentifier
	hashOpt, err := bccsp.GetHashOpt(msp.cryptoConfig.IdentityIdentifierHashFunction)
	if err != nil {
		return nil, errors.WithMessage(err, "failed getting hash function options")
	}

	digest, err := msp.bccsp.Hash(cert.Raw, hashOpt)
	if err != nil {
		return nil, errors.WithMessage(err, "failed hashing raw certificate to compute the id of the IdentityIdentifier")
	}

	id := &IdentityIdentifier{
		Mspid: msp.name,
		Id:    hex.EncodeToString(digest)}

	return &identity{id: id, cert: cert, pk: pk, msp: msp}, nil
}

// ExpiresAt returns the time at which the Identity expires.
func (id *identity) ExpiresAt() time.Time {
	return id.cert.NotAfter
}

// SatisfiesPrincipal returns nil if this instance matches the supplied principal or an error otherwise
func (id *identity) SatisfiesPrincipal(principal *msp.MSPPrincipal) error {
	return id.msp.SatisfiesPrincipal(id, principal)
}

// GetIdentifier returns the identifier (MSPID/IDID) for this instance
func (id *identity) GetIdentifier() *IdentityIdentifier {
	return id.id
}

// GetMSPIdentifier returns the MSP identifier for this instance
func (id *identity) GetMSPIdentifier() string {
	return id.id.Mspid
}

// Validate returns nil if this instance is a valid identity or an error otherwise
func (id *identity) Validate() error {
	return id.msp.Validate(id)
}

type OUIDs []*OUIdentifier

func (o OUIDs) String() string {
	var res []string
	for _, id := range o {
		res = append(res, fmt.Sprintf("%s(%X)", id.OrganizationalUnitIdentifier, id.CertifiersIdentifier[0:8]))
	}

	return fmt.Sprintf("%s", res)
}

// GetOrganizationalUnits returns the OU for this instance
func (id *identity) GetOrganizationalUnits() []*OUIdentifier {
	if id.cert == nil {
		return nil
	}

	cid, err := id.msp.getCertificationChainIdentifier(id)
	if err != nil {
		mspIdentityLogger.Errorf("Failed getting certification chain identifier for [%v]: [%+v]", id, err)

		return nil
	}

	var res []*OUIdentifier
	for _, unit := range id.cert.Subject.OrganizationalUnit {
		res = append(res, &OUIdentifier{
			OrganizationalUnitIdentifier: unit,
			CertifiersIdentifier:         cid,
		})
	}

	return res
}

// Anonymous returns true if this identity provides anonymity
func (id *identity) Anonymous() bool {
	return false
}

// Verify checks against a signature and a message
// to determine whether this identity produced the
// signature; it returns nil if so or an error otherwise
func (id *identity) Verify(msg []byte, sig []byte) error {
	// mspIdentityLogger.Infof("Verifying signature")

	// Compute Hash
	var hashOpt fbccsp.HashOpts
	var digest []byte
	var err error
	var signerOption fbccsp.SignerOpts

	if id.pk != nil && id.pk.Algorithm() == fbccsp.SM2 {
		digest = msg
		signerOption = fbccsp.SignerOpts(cspx509.SM3)
	}else {
		hashOpt, err := id.getHashOpt(id.msp.cryptoConfig.SignatureHashFamily)
		if err != nil {
			return errors.WithMessage(err, "failed getting hash function options")
		}

		digest, err = id.msp.bccsp.Hash(msg, hashOpt)
		if err != nil {
			return errors.WithMessage(err, "failed computing digest")
		}
		signerOption = nil
	}

	if mspIdentityLogger.IsEnabledFor(logging.DEBUG) {
		mspIdentityLogger.Debugf("Verify: hash type = %s", id.pk.Algorithm())
		mspIdentityLogger.Debugf("Verify: digest = %s", hex.Dump(digest))
		mspIdentityLogger.Debugf("Verify: sig = %s", hex.Dump(sig))
	}

	valid, err := id.msp.bccsp.Verify(id.pk, sig, digest, signerOption)
	if err != nil || !valid {
		if id.pk != nil && id.pk.Algorithm() == fbccsp.SM2 {
			hashOpt, err = id.getHashOpt(id.msp.cryptoConfig.SignatureHashFamily)
			if err != nil {
				return errors.WithMessage(err, "second trial for SM2 failed getting hash function options")
			}

			digest, err = id.msp.bccsp.Hash(msg, hashOpt)
			if err != nil {
				return errors.WithMessage(err, "second trial for SM2 failed computing digest")
			}
		}
		valid, err = id.msp.bccsp.Verify(id.pk, sig, digest, nil)
		if err != nil {
			return errors.WithMessage(err, "could not determine the validity of the signature")
		} else if !valid {
			return errors.New("The signature is invalid")
		}
	}

	return nil
}

// Serialize returns a byte array representation of this identity
func (id *identity) Serialize() ([]byte, error) {
	pb := &pem.Block{Bytes: id.cert.Raw, Type: "CERTIFICATE"}
	pemBytes := pem.EncodeToMemory(pb)
	if pemBytes == nil {
		return nil, errors.New("encoding of identity failed")
	}

	// We serialize identities by prepending the MSPID and appending the ASN.1 DER content of the cert
	sId := &msp.SerializedIdentity{Mspid: id.id.Mspid, IdBytes: pemBytes}
	idBytes, err := proto.Marshal(sId)
	if err != nil {
		return nil, errors.Wrapf(err, "could not marshal a SerializedIdentity structure for identity %s", id.id)
	}

	return idBytes, nil
}

func (id *identity) getHashOpt(hashFamily string) (core.HashOpts, error) {
	switch hashFamily {
	case fbccsp.SHA2:
		return bccsp.GetHashOpt(fbccsp.SHA256)
	case fbccsp.SHA3:
		return bccsp.GetHashOpt(fbccsp.SHA3_256)
	case fbccsp.SM3:
		return bccsp.GetHashOpt(fbccsp.SM3)
	}
	return nil, errors.Errorf("hash familiy not recognized [%s]", hashFamily)
}

type signingidentity struct {
	// we embed everything from a base identity
	identity

	// signer corresponds to the object that can produce signatures from this identity
	signer crypto.Signer
}

func newSigningIdentity(cert *x509.Certificate, pk core.Key, signer crypto.Signer, msp *bccspmsp) (SigningIdentity, error) {
	//mspIdentityLogger.Infof("Creating signing identity instance for ID %s", id)
	mspId, err := newIdentity(cert, pk, msp)
	if err != nil {
		return nil, err
	}
	return &signingidentity{
		identity: identity{
			id:   mspId.(*identity).id,
			cert: mspId.(*identity).cert,
			msp:  mspId.(*identity).msp,
			pk:   mspId.(*identity).pk,
		},
		signer: signer,
	}, nil
}

// Sign produces a signature over msg, signed by this instance
func (id *signingidentity) Sign(msg []byte) ([]byte, error) {
	//mspIdentityLogger.Infof("Signing message")

	// Compute Hash
	var hashOpt fbccsp.HashOpts
	var digest []byte
	var err error
	var signerOption fbccsp.SignerOpts

	mspIdentityLogger.Infof("pk: %s", id.pk.Algorithm())
	if id.pk.Algorithm() == fbccsp.SM2  {
		digest = msg
		signerOption = fbccsp.SignerOpts(cspx509.SM3)
	} else {
		hashOpt, err = id.getHashOpt(id.msp.cryptoConfig.SignatureHashFamily)
		if err != nil {
			return nil, errors.WithMessage(err, "failed getting hash function options")
		}

		digest, err = id.msp.bccsp.Hash(msg, hashOpt)
		if err != nil {
			return nil, errors.WithMessage(err, "failed computing digest")
		}
		signerOption = nil
	}

	if mspIdentityLogger.IsEnabledFor(logging.DEBUG) {
		mspIdentityLogger.Debugf("Sign: sig type = %s", id.pk.Algorithm())
	}

	if len(msg) < 32 {
		mspIdentityLogger.Debugf("Sign: plaintext: %X \n", msg)
	} else {
		mspIdentityLogger.Debugf("Sign: plaintext: %X...%X \n", msg[0:16], msg[len(msg)-16:])
	}
	mspIdentityLogger.Debugf("Sign: digest: %X \n", digest)

	// Sign
	return id.signer.Sign(rand.Reader, digest, signerOption)
}

// GetPublicVersion returns the public version of this identity,
// namely, the one that is only able to verify messages and not sign them
func (id *signingidentity) GetPublicVersion() Identity {
	return &id.identity
}
