package bsv

import "encoding/base64"

// Message mirrors lib/message/message (Bitcoin signed messages).
type Message struct {
	MessageBuffer []byte
	Error         string
}

// NewMessage creates a Message from a string or buffer.
func NewMessage(message interface{}) (*Message, error) {
	switch v := message.(type) {
	case string:
		return &Message{MessageBuffer: []byte(v)}, nil
	case []byte:
		return &Message{MessageBuffer: v}, nil
	default:
		return nil, newError("InvalidArgument", "First argument should be a string or Buffer")
	}
}

// MagicBytes is the Bitcoin signed message prefix.
var MagicBytes = []byte("Bitcoin Signed Message:\n")

// MagicHash computes the double-SHA256 of the prefixed message.
func (m *Message) MagicHash() []byte {
	prefix1 := varintBufNum(len(MagicBytes))
	prefix2 := varintBufNum(len(m.MessageBuffer))
	buf := concatBytes(prefix1, MagicBytes, prefix2, m.MessageBuffer)
	return (Hash{}).Sha256sha256(buf)
}

// Sign signs the message with a private key, returning a base64 compact sig.
func (m *Message) Sign(privateKey *PrivateKey) (string, error) {
	hash := m.MagicHash()
	sig, err := ECDSASignWithCalcI(hash, privateKey, "")
	if err != nil {
		return "", err
	}
	compact, err := sig.ToCompact(sig.I, sig.Compressed)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(compact), nil
}

// Verify checks a base64 compact signature against an address.
func (m *Message) Verify(bitcoinAddress interface{}, signatureString string) (bool, error) {
	if bitcoinAddress == nil {
		return false, newError("InvalidArgument", "bitcoinAddress is required")
	}
	if signatureString == "" {
		return false, newError("InvalidArgument", "signatureString is required")
	}
	var address *Address
	var err error
	switch v := bitcoinAddress.(type) {
	case *Address:
		address = v
	case string:
		address, err = AddressFromString(v, nil, "")
		if err != nil {
			return false, err
		}
	default:
		return false, newError("InvalidArgument", "bitcoinAddress must be an Address or string")
	}
	sigBytes, err := base64.StdEncoding.DecodeString(signatureString)
	if err != nil {
		return false, err
	}
	signature, err := SignatureFromCompact(sigBytes)
	if err != nil {
		return false, err
	}

	// recover the public key
	ecdsa := NewECDSA(&ECDSA{Hashbuf: m.MagicHash(), Sig: signature})
	pubkey, err := ecdsa.ToPublicKey()
	if err != nil {
		return false, err
	}

	signatureAddress, err := AddressFromPublicKey(pubkey, address.Network)
	if err != nil {
		return false, err
	}
	if address.ToString() != signatureAddress.ToString() {
		m.Error = "The signature did not match the message digest"
		return false, nil
	}

	return m.verifyWithPublicKey(pubkey, signature)
}

func (m *Message) verifyWithPublicKey(pubkey *PublicKey, signature *Signature) (bool, error) {
	hash := m.MagicHash()
	verified := ECDSAVerify(hash, signature, pubkey, "")
	if !verified {
		m.Error = "The signature was invalid"
	}
	return verified, nil
}

// ToString returns the message text.
func (m *Message) ToString() string { return string(m.MessageBuffer) }

// ToObject returns a plain object representation.
func (m *Message) ToObject() map[string]interface{} {
	return map[string]interface{}{"messageHex": toHex(m.MessageBuffer)}
}