package pb

import (
	"crypto/sha256"
	"fmt"
	"math/big"

	"github.com/gogo/protobuf/proto"
	"github.com/meshplus/bitxhub-kit/crypto"
	"github.com/meshplus/bitxhub-kit/crypto/asym"
	"github.com/meshplus/bitxhub-kit/types"
)

var _ Transaction = (*BxhTransaction)(nil)

func init() {
	RegisterTxConstructor(0, func() Transaction {
		return &BxhTransaction{}
	})
}

func (m *BxhTransaction) Hash() *types.Hash {
	tx := &BxhTransaction{
		From:      m.From,
		To:        m.To,
		Timestamp: m.Timestamp,
		Payload:   m.Payload,
		IBTP:      m.IBTP,
		Nonce:     m.Nonce,
		Amount:    m.Amount,
		Signature: m.Signature,
	}

	body, err := tx.Marshal()
	if err != nil {
		panic(err)
	}

	data := sha256.Sum256(body)

	return types.NewHash(data[:])
}

func (m *BxhTransaction) SignHash() *types.Hash {
	tx := &BxhTransaction{
		From:      m.From,
		To:        m.To,
		Timestamp: m.Timestamp,
		Payload:   m.Payload,
		IBTP:      m.IBTP,
		Nonce:     m.Nonce,
		Amount:    m.Amount,
	}

	body, err := tx.Marshal()
	if err != nil {
		panic(err)
	}

	ret := sha256.Sum256(body)

	return types.NewHash(ret[:])
}

func (m *BxhTransaction) Sign(key crypto.PrivateKey) error {
	sign, err := key.Sign(m.SignHash().Bytes())
	if err != nil {
		return err
	}

	m.Signature = sign

	return nil
}

func (m *BxhTransaction) GetCrosschainExtra() (*CrosschainTransactionExtra, error) {
	extra := &CrosschainTransactionExtra{}
	if err := proto.Unmarshal(m.Extra, extra); err != nil {
		return nil, err
	}

	return extra, nil
}

func (m *BxhTransaction) IsIBTP() bool {
	return m.IBTP != nil
}

func (m *BxhTransaction) Account() string {
	return m.From.String()
}

func (m *BxhTransaction) GetFrom() *types.Address {
	return m.From
}

func (m *BxhTransaction) GetTo() *types.Address {
	return m.To
}

func (m *BxhTransaction) GetTimeStamp() int64 {
	return m.Timestamp
}

func (m *BxhTransaction) GetHash() *types.Hash {
	if m.TransactionHash == nil {
		m.TransactionHash = m.Hash()
	}

	return m.TransactionHash
}

func (m *BxhTransaction) GetGas() uint64 {
	return 0
}

func (m *BxhTransaction) GetGasPrice() *big.Int {
	return big.NewInt(0)
}

func (m *BxhTransaction) GetChainID() *big.Int {
	return big.NewInt(0)
}

func (m *BxhTransaction) MarshalWithFlag() ([]byte, error) {
	data, err := m.Marshal()
	if err != nil {
		return nil, err
	}

	txData := append([]byte{0}, data...)

	return txData, nil
}

func (m *BxhTransaction) SizeWithFlag() int {
	return m.Size() + 1
}

func (m *BxhTransaction) GetSignHash() *types.Hash {
	return m.SignHash()
}

// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (m *BxhTransaction) GetRawSignature() (v, r, s *big.Int) {
	if len(m.Signature) != 65 {
		return nil, nil, nil
	}

	r = &big.Int{}
	r.SetBytes(m.Signature[:32])
	s = &big.Int{}
	s.SetBytes(m.Signature[32:64])
	v = &big.Int{}
	v.SetBytes(m.Signature[64:])

	return v, r, s
}

// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (m *BxhTransaction) GetType() byte {
	return BxhTxType
}

func (m *BxhTransaction) VerifySignature() error {
	ok, err := asym.Verify(crypto.Secp256k1, m.GetSignature(), m.GetSignHash().Bytes(), *m.GetFrom())
	if err != nil {
		return err
	}

	if !ok {
		return fmt.Errorf("invalid signature")
	}

	return nil
}