package globalplatform

import (
	"bytes"
	"encoding/binary"

	"github.com/status-im/keycard-go/apdu"
	"github.com/status-im/keycard-go/globalplatform/crypto"
)

// SCP02Wrapper is a wrapper for apdu commands inside a global platform secure channel.
type SCP02Wrapper struct {
	macKey []byte
	icv    []byte
}

// NewSCP02Wrapper returns a new SCP02Wrapper using the specified key for MAC generation.
func NewSCP02Wrapper(macKey []byte) *SCP02Wrapper {
	return &SCP02Wrapper{
		macKey: macKey,
		icv:    crypto.NullBytes8,
	}
}

// Wrap wraps the apdu command adding the MAC to the end of the command.
// Future implementations will encrypt the message when needed.
func (w *SCP02Wrapper) Wrap(cmd *apdu.Command) (*apdu.Command, error) {
	macData := new(bytes.Buffer)

	cla := cmd.Cla | 0x04
	if err := binary.Write(macData, binary.BigEndian, cla); err != nil {
		return nil, err
	}

	if err := binary.Write(macData, binary.BigEndian, cmd.Ins); err != nil {
		return nil, err
	}

	if err := binary.Write(macData, binary.BigEndian, cmd.P1); err != nil {
		return nil, err
	}

	if err := binary.Write(macData, binary.BigEndian, cmd.P2); err != nil {
		return nil, err
	}

	if err := binary.Write(macData, binary.BigEndian, uint8(len(cmd.Data)+8)); err != nil {
		return nil, err
	}

	if err := binary.Write(macData, binary.BigEndian, cmd.Data); err != nil {
		return nil, err
	}

	var (
		icv []byte
		err error
	)

	if bytes.Equal(w.icv, crypto.NullBytes8) {
		icv = w.icv
	} else {
		icv, err = crypto.EncryptICV(w.macKey, w.icv)
		if err != nil {
			return nil, err
		}
	}

	mac, err := crypto.MacFull3DES(w.macKey, macData.Bytes(), icv)
	if err != nil {
		return nil, err
	}

	newData := make([]byte, 0)
	newData = append(newData, cmd.Data...)
	newData = append(newData, mac...)

	w.icv = mac

	newCmd := apdu.NewCommand(cla, cmd.Ins, cmd.P1, cmd.P2, newData)
	if ok, le := cmd.Le(); ok {
		newCmd.SetLe(le)
	}

	return newCmd, nil
}
