package encryptor

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/jchavannes/go-pgp/pgp"
	"golang.org/x/crypto/openpgp"
	"golang.org/x/crypto/openpgp/armor"
)

// Encryptor interface
type Encryptor interface {
	Encrypt(string) string
	Decrypt() string
}

type encryptor struct {
	entity *openpgp.Entity
}

type readerWriter struct {
	b []byte
}

func (rw *readerWriter) Write(b []byte) (int, error) {
	rw.b = b[:]
	return len(b), io.EOF
}

func (rw *readerWriter) Read(b []byte) (int, error) {
	b = rw.b[:]
	return len(b), io.EOF
}

func NewKeyPair() (string, string) {
	entity, err := openpgp.NewEntity("a", "b", "c@d.e", nil)
	if err != nil {
		panic(err)
	}

	// Sign all the identities
	for _, id := range entity.Identities {
		err := id.SelfSignature.SignUserId(id.UserId.Id, entity.PrimaryKey, entity.PrivateKey, nil)
		if err != nil {
			panic(err)
		}
	}

	rw := &readerWriter{b: make([]byte, 5000)}
	b := make([]byte, 5000)

	priW, err := armor.Encode(rw, openpgp.PrivateKeyType, nil)
	if err != io.EOF {
		fmt.Println(priW, rw)
		panic(err)
	}
	if priW != nil {
		defer priW.Close()
		entity.SerializePrivate(priW, nil)
	}
	rw.Read(b)
	pri := string(b)

	pubW, err := armor.Encode(rw, openpgp.PublicKeyType, nil)
	if err != io.EOF {
		panic(err)
	}
	rw.Read(b)
	pub := string(b)
	if pubW != nil {
		defer pubW.Close()
		entity.Serialize(pubW)
	}
	fmt.Println(pri, pub)

	return pri, pub
}

// NewEncryptor function
func NewEncryptor(private, public string) *encryptor {
	pubKeyPack, err := pgp.GetPublicKeyPacket([]byte(public))
	if err != nil {
		panic(err)
	}
	priKeyPack, err := pgp.GetPrivateKeyPacket([]byte(private))
	if err != nil {
		panic(err)
	}
	entity, err := openpgp.NewEntity("", "", "", nil)
	entity.PrimaryKey = pubKeyPack
	entity.PrivateKey = priKeyPack
	return &encryptor{
		entity: entity,
	}
}

func b64enc(msg io.Reader) (string, error) {
	bytes, err := ioutil.ReadAll(msg)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(bytes), nil
}

func b64dec(msg string) (io.Reader, error) {
	decMe, err := base64.StdEncoding.DecodeString(msg)
	if err != nil {
		return nil, err
	}
	return bytes.NewBuffer(decMe), nil
}

// Encrypt function
func (e *encryptor) Encrypt(msg string) (string, error) {
	// encrypt string
	buf := new(bytes.Buffer)
	w, err := openpgp.Encrypt(buf, []*openpgp.Entity{e.entity}, nil, nil, nil)
	if err != nil {
		return "", err
	}
	_, err = w.Write([]byte(msg))
	if err != nil {
		return "", err
	}
	err = w.Close()
	if err != nil {
		return "", err
	}
	// Encode to base64
	return b64enc(buf)
}

// Decrypt function
func (e *encryptor) Decrypt(msg string) (string, error) {
	// Decode the base64 string
	decMe, err := b64dec(msg)
	if err != nil {
		panic(err)
	}

	md, err := openpgp.ReadMessage(decMe, openpgp.EntityList([]*openpgp.Entity{e.entity}), nil, nil)
	if err != nil {
		return "", err
	}

	plaintext, err := ioutil.ReadAll(md.UnverifiedBody)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}
