/*
Copyright: Cognition Foundry. All Rights Reserved.
License: Apache License Version 2.0
*/
package gohfc

import (
	"crypto/ecdsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"io/ioutil"
)

// Identity is participant public and private key
type Identity struct {
	Certificate *x509.Certificate
	PrivateKey  interface{}
	MspId       string
}

// EnrollmentId get enrollment id from certificate
func (i *Identity) EnrollmentId() string {
	return i.Certificate.Subject.CommonName
}

// EnrollmentId get enrollment id from certificate
func (i *Identity) ToPem() ([]byte, []byte, error) {

	switch i.PrivateKey.(type) {
	case *ecdsa.PrivateKey:
		cast := i.PrivateKey.(*ecdsa.PrivateKey)
		b, err := x509.MarshalECPrivateKey(cast)
		if err != nil {
			return nil, nil, err
		}
		privateKey := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
		cert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: i.Certificate.Raw})
		return cert, privateKey, nil

	default:
		return nil, nil, ErrInvalidKeyType
	}
}

// MarshalIdentity marshal identity to string
func MarshalIdentity(i *Identity) (string, error) {

	var pk, cert string
	switch i.PrivateKey.(type) {
	case *ecdsa.PrivateKey:
		cast := i.PrivateKey.(*ecdsa.PrivateKey)
		b, err := x509.MarshalECPrivateKey(cast)
		if err != nil {
			return "", err
		}
		block := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
		pk = base64.RawStdEncoding.EncodeToString(block)

	default:
		return "", ErrInvalidKeyType
	}

	cert = base64.RawStdEncoding.EncodeToString(i.Certificate.Raw)
	str, err := json.Marshal(map[string]string{"cert": cert, "pk": pk, "mspid": i.MspId})
	if err != nil {
		return "", err
	}

	return string(str), nil
}

// UnmarshalIdentity unmarshal identity from string
func UnmarshalIdentity(data string) (*Identity, error) {
	var raw map[string]string
	if err := json.Unmarshal([]byte(data), &raw); err != nil {
		return nil, err
	}
	// check do we have all keys
	if _, ok := raw["cert"]; !ok || len(raw["cert"]) < 1 {
		return nil, ErrInvalidDataForParcelIdentity
	}
	if _, ok := raw["pk"]; !ok || len(raw["pk"]) < 1 {
		return nil, ErrInvalidDataForParcelIdentity
	}

	certRaw, err := base64.RawStdEncoding.DecodeString(raw["cert"])
	if err != nil {
		return nil, err
	}
	cert, err := x509.ParseCertificate(certRaw)
	if err != nil {
		return nil, err
	}

	keyRaw, err := base64.RawStdEncoding.DecodeString(raw["pk"])
	if err != nil {
		return nil, err
	}
	keyPem, _ := pem.Decode(keyRaw)
	if keyPem == nil {
		return nil, ErrInvalidDataForParcelIdentity
	}
	var pk interface{}
	switch keyPem.Type {
	case "EC PRIVATE KEY":
		pk, err = x509.ParseECPrivateKey(keyPem.Bytes)
		if err != nil {
			return nil, ErrInvalidDataForParcelIdentity
		}
	default:
		return nil, ErrInvalidDataForParcelIdentity
	}

	identity := &Identity{Certificate: cert, PrivateKey: pk, MspId: raw["mspid"]}
	return identity, nil

}

// LoadCertFromFile read public key (pk) and private/secret kye (sk) from file system and return new Identity
func LoadCertFromFile(pk, sk string) (*Identity, error) {
	cf, err := ioutil.ReadFile(pk)
	if err != nil {
		return nil, err
	}

	kf, err := ioutil.ReadFile(sk)
	if err != nil {
		return nil, err
	}
	cpb, _ := pem.Decode(cf)
	kpb, _ := pem.Decode(kf)
	crt, err := x509.ParseCertificate(cpb.Bytes)
	if err != nil {
		return nil, err
	}
	key, err := x509.ParsePKCS8PrivateKey(kpb.Bytes)
	if err != nil {
		return nil, err
	}
	return &Identity{Certificate: crt, PrivateKey: key}, nil
}