package ibctesting

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"git.cw.tr/mukan-network/mukan-sdk/codec"
	codectypes "git.cw.tr/mukan-network/mukan-sdk/codec/types"
	kmultisig "git.cw.tr/mukan-network/mukan-sdk/crypto/keys/multisig"
	"git.cw.tr/mukan-network/mukan-sdk/crypto/keys/secp256k1"
	cryptotypes "git.cw.tr/mukan-network/mukan-sdk/crypto/types"
	"git.cw.tr/mukan-network/mukan-sdk/crypto/types/multisig"
	"git.cw.tr/mukan-network/mukan-sdk/types/tx/signing"

	transfertypes "git.cw.tr/mukan-network/mukan-ibc/modules/apps/transfer/types"
	clienttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/02-client/types"
	connectiontypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/03-connection/types"
	channeltypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/04-channel/types"
	commitmenttypes "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types"
	commitmenttypesv2 "git.cw.tr/mukan-network/mukan-ibc/modules/core/23-commitment/types/v2"
	host "git.cw.tr/mukan-network/mukan-ibc/modules/core/24-host"
	"git.cw.tr/mukan-network/mukan-ibc/modules/core/exported"
	solomachine "git.cw.tr/mukan-network/mukan-ibc/modules/light-clients/06-solomachine"
)

var (
	clientIDSolomachine     = "client-on-solomachine"     // clientID generated on solo machine side
	connectionIDSolomachine = "connection-on-solomachine" // connectionID generated on solo machine side
	channelIDSolomachine    = "channel-on-solomachine"    // channelID generated on solo machine side
)

// DefaultSolomachineClientID is the default solo machine client id used for testing
var DefaultSolomachineClientID = "06-solomachine-0"

// Solomachine is a testing helper used to simulate a counterparty
// solo machine client.
type Solomachine struct {
	t *testing.T

	cdc         codec.BinaryCodec
	ClientID    string
	PrivateKeys []cryptotypes.PrivKey // keys used for signing
	PublicKeys  []cryptotypes.PubKey  // keys used for generating solo machine pub key
	PublicKey   cryptotypes.PubKey    // key used for verification
	Sequence    uint64
	Time        uint64
	Diversifier string
}

// NewSolomachine returns a new solomachine instance with an `nKeys` amount of
// generated private/public key pairs and a sequence starting at 1. If nKeys
// is greater than 1 then a multisig public key is used.
func NewSolomachine(t *testing.T, cdc codec.BinaryCodec, clientID, diversifier string, nKeys uint64) *Solomachine {
	t.Helper()
	privKeys, pubKeys, pk := GenerateKeys(t, nKeys)

	return &Solomachine{
		t:           t,
		cdc:         cdc,
		ClientID:    clientID,
		PrivateKeys: privKeys,
		PublicKeys:  pubKeys,
		PublicKey:   pk,
		Sequence:    1,
		Time:        10,
		Diversifier: diversifier,
	}
}

// GenerateKeys generates a new set of secp256k1 private keys and public keys.
// If the number of keys is greater than one then the public key returned represents
// a multisig public key. The private keys are used for signing, the public
// keys are used for generating the public key and the public key is used for
// solo machine verification. The usage of secp256k1 is entirely arbitrary.
// The key type can be swapped for any key type supported by the PublicKey
// interface, if needed. The same is true for the amino based Multisignature
// public key.
func GenerateKeys(t *testing.T, n uint64) ([]cryptotypes.PrivKey, []cryptotypes.PubKey, cryptotypes.PubKey) {
	t.Helper()
	require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed")

	privKeys := make([]cryptotypes.PrivKey, n)
	pubKeys := make([]cryptotypes.PubKey, n)
	for i := uint64(0); i < n; i++ {
		privKeys[i] = secp256k1.GenPrivKey()
		pubKeys[i] = privKeys[i].PubKey()
	}

	var pk cryptotypes.PubKey
	if len(privKeys) > 1 {
		// generate multi sig pk
		pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys)
	} else {
		pk = privKeys[0].PubKey()
	}

	return privKeys, pubKeys, pk
}

// ClientState returns a new solo machine ClientState instance.
func (solo *Solomachine) ClientState() *solomachine.ClientState {
	return solomachine.NewClientState(solo.Sequence, solo.ConsensusState())
}

// ConsensusState returns a new solo machine ConsensusState instance
func (solo *Solomachine) ConsensusState() *solomachine.ConsensusState {
	publicKey, err := codectypes.NewAnyWithValue(solo.PublicKey)
	require.NoError(solo.t, err)

	return &solomachine.ConsensusState{
		PublicKey:   publicKey,
		Diversifier: solo.Diversifier,
		Timestamp:   solo.Time,
	}
}

// GetHeight returns an exported.Height with Sequence as RevisionHeight
func (solo *Solomachine) GetHeight() exported.Height {
	return clienttypes.NewHeight(0, solo.Sequence)
}

// CreateClient creates an on-chain client on the provided chain.
func (solo *Solomachine) CreateClient(chain *TestChain) string {
	msgCreateClient, err := clienttypes.NewMsgCreateClient(solo.ClientState(), solo.ConsensusState(), chain.SenderAccount.GetAddress().String())
	require.NoError(solo.t, err)

	res, err := chain.SendMsgs(msgCreateClient)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)

	clientID, err := ParseClientIDFromEvents(res.Events)
	require.NoError(solo.t, err)

	return clientID
}

// UpdateClient sends a MsgUpdateClient to the provided chain and updates the given clientID.
func (solo *Solomachine) UpdateClient(chain *TestChain, clientID string) {
	smHeader := solo.CreateHeader(solo.Diversifier)
	msgUpdateClient, err := clienttypes.NewMsgUpdateClient(clientID, smHeader, chain.SenderAccount.GetAddress().String())
	require.NoError(solo.t, err)

	res, err := chain.SendMsgs(msgUpdateClient)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// CreateHeader generates a new private/public key pair and creates the
// necessary signature to construct a valid solo machine header.
// A new diversifier will be used as well
func (solo *Solomachine) CreateHeader(newDiversifier string) *solomachine.Header {
	// generate new private keys and signature for header
	newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys)))

	publicKey, err := codectypes.NewAnyWithValue(newPubKey)
	require.NoError(solo.t, err)

	data := &solomachine.HeaderData{
		NewPubKey:      publicKey,
		NewDiversifier: newDiversifier,
	}

	dataBz, err := solo.cdc.Marshal(data)
	require.NoError(solo.t, err)

	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        []byte(solomachine.SentinelHeaderPath),
		Data:        dataBz,
	}

	bz, err := solo.cdc.Marshal(signBytes)
	require.NoError(solo.t, err)

	sig := solo.GenerateSignature(bz)

	header := &solomachine.Header{
		Timestamp:      solo.Time,
		Signature:      sig,
		NewPublicKey:   publicKey,
		NewDiversifier: newDiversifier,
	}

	// assumes successful header update
	solo.Sequence++
	solo.Time++
	solo.PrivateKeys = newPrivKeys
	solo.PublicKeys = newPubKeys
	solo.PublicKey = newPubKey
	solo.Diversifier = newDiversifier

	return header
}

// CreateMisbehaviour constructs testing misbehaviour for the solo machine client
// by signing over two different data bytes at the same sequence.
func (solo *Solomachine) CreateMisbehaviour() *solomachine.Misbehaviour {
	merklePath := commitmenttypes.NewMerklePath(host.FullClientStateKey("counterparty"))
	path, err := solo.cdc.Marshal(&merklePath)
	require.NoError(solo.t, err)

	data, err := solo.cdc.Marshal(solo.ClientState())
	require.NoError(solo.t, err)

	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        data,
	}

	bz, err := solo.cdc.Marshal(signBytes)
	require.NoError(solo.t, err)

	sig := solo.GenerateSignature(bz)
	signatureOne := solomachine.SignatureAndData{
		Signature: sig,
		Path:      path,
		Data:      data,
		Timestamp: solo.Time,
	}

	// misbehaviour signaturess can have different timestamps
	solo.Time++

	merklePath = commitmenttypes.NewMerklePath(host.FullConsensusStateKey("counterparty", clienttypes.NewHeight(0, 1)))
	path, err = solo.cdc.Marshal(&merklePath)
	require.NoError(solo.t, err)

	data, err = solo.cdc.Marshal(solo.ConsensusState())
	require.NoError(solo.t, err)

	signBytes = &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        data,
	}

	bz, err = solo.cdc.Marshal(signBytes)
	require.NoError(solo.t, err)

	sig = solo.GenerateSignature(bz)
	signatureTwo := solomachine.SignatureAndData{
		Signature: sig,
		Path:      path,
		Data:      data,
		Timestamp: solo.Time,
	}

	return &solomachine.Misbehaviour{
		Sequence:     solo.Sequence,
		SignatureOne: &signatureOne,
		SignatureTwo: &signatureTwo,
	}
}

// ConnOpenInit initializes a connection on the provided chain given a solo machine clientID.
func (solo *Solomachine) ConnOpenInit(chain *TestChain, clientID string) string {
	msgConnOpenInit := connectiontypes.NewMsgConnectionOpenInit(
		clientID,
		clientIDSolomachine, // clientID generated on solo machine side
		chain.GetPrefix(), DefaultOpenInitVersion, DefaultDelayPeriod,
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgConnOpenInit)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)

	connectionID, err := ParseConnectionIDFromEvents(res.Events)
	require.NoError(solo.t, err)

	return connectionID
}

// ConnOpenAck performs the connection open ack handshake step on the tendermint chain for the associated
// solo machine client.
func (solo *Solomachine) ConnOpenAck(chain *TestChain, clientID, connectionID string) {
	tryProof := solo.GenerateConnOpenTryProof(clientID, connectionID)

	msgConnOpenAck := connectiontypes.NewMsgConnectionOpenAck(
		connectionID, connectionIDSolomachine, tryProof,
		clienttypes.ZeroHeight(), ConnectionVersion,
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgConnOpenAck)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// ChanOpenInit initializes a channel on the provided chain given a solo machine connectionID.
func (solo *Solomachine) ChanOpenInit(chain *TestChain, connectionID string) string {
	msgChanOpenInit := channeltypes.NewMsgChannelOpenInit(
		transfertypes.PortID,
		transfertypes.V1,
		channeltypes.UNORDERED,
		[]string{connectionID},
		transfertypes.PortID,
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgChanOpenInit)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)

	channelID, err := ParseChannelIDFromEvents(res.Events)
	require.NoError(solo.t, err)

	return channelID
}

// ChanOpenAck performs the channel open ack handshake step on the tendermint chain for the associated
// solo machine client.
func (solo *Solomachine) ChanOpenAck(chain *TestChain, channelID string) {
	tryProof := solo.GenerateChanOpenTryProof(transfertypes.PortID, transfertypes.V1, channelID)
	msgChanOpenAck := channeltypes.NewMsgChannelOpenAck(
		transfertypes.PortID,
		channelID,
		channelIDSolomachine,
		transfertypes.V1,
		tryProof,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgChanOpenAck)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// ChanCloseConfirm performs the channel close confirm handshake step on the tendermint chain for the associated
// solo machine client.
func (solo *Solomachine) ChanCloseConfirm(chain *TestChain, portID, channelID string) {
	initProof := solo.GenerateChanClosedProof(portID, transfertypes.V1, channelID)
	msgChanCloseConfirm := channeltypes.NewMsgChannelCloseConfirm(
		portID,
		channelID,
		initProof,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgChanCloseConfirm)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// SendTransfer constructs a MsgTransfer and sends the message to the given chain. Any number of optional
// functions can be provided which will modify the MsgTransfer before SendMsgs is called.
func (solo *Solomachine) SendTransfer(chain *TestChain, portID, channelID string, fns ...func(*transfertypes.MsgTransfer)) channeltypes.Packet {
	msgTransfer := transfertypes.NewMsgTransfer(
		portID,
		channelID,
		TestCoin,
		chain.SenderAccount.GetAddress().String(),
		chain.SenderAccount.GetAddress().String(),
		clienttypes.ZeroHeight(),
		uint64(chain.GetContext().BlockTime().Add(time.Hour).UnixNano()),
		"",
	)

	for _, fn := range fns {
		fn(msgTransfer)
	}

	res, err := chain.SendMsgs(msgTransfer)
	require.NoError(solo.t, err)

	packet, err := ParseV1PacketFromEvents(res.Events)
	require.NoError(solo.t, err)

	return packet
}

// RecvPacket creates a commitment proof and broadcasts a new MsgRecvPacket.
func (solo *Solomachine) RecvPacket(chain *TestChain, packet channeltypes.Packet) {
	proofCommitment := solo.GenerateCommitmentProof(packet)
	msgRecvPacket := channeltypes.NewMsgRecvPacket(
		packet,
		proofCommitment,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgRecvPacket)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// AcknowledgePacket creates an acknowledgement proof and broadcasts a MsgAcknowledgement.
func (solo *Solomachine) AcknowledgePacket(chain *TestChain, packet channeltypes.Packet) {
	ackProof := solo.GenerateAcknowledgementProof(packet)
	transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement()
	msgAcknowledgement := channeltypes.NewMsgAcknowledgement(
		packet, transferAck,
		ackProof,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgAcknowledgement)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// TimeoutPacket creates a unreceived packet proof and broadcasts a MsgTimeout.
func (solo *Solomachine) TimeoutPacket(chain *TestChain, packet channeltypes.Packet) {
	unreceivedProof := solo.GenerateReceiptAbsenceProof(packet)
	msgTimeout := channeltypes.NewMsgTimeout(
		packet,
		1, // nextSequenceRecv is unused for UNORDERED channels
		unreceivedProof,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgTimeout)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// TimeoutPacketOnClose creates a channel closed and unreceived packet proof and broadcasts a MsgTimeoutOnClose.
func (solo *Solomachine) TimeoutPacketOnClose(chain *TestChain, packet channeltypes.Packet, channelID string) {
	closedProof := solo.GenerateChanClosedProof(transfertypes.PortID, transfertypes.V1, channelID)
	unreceivedProof := solo.GenerateReceiptAbsenceProof(packet)
	msgTimeout := channeltypes.NewMsgTimeoutOnClose(
		packet,
		1, // nextSequenceRecv is unused for UNORDERED channels
		unreceivedProof,
		closedProof,
		clienttypes.ZeroHeight(),
		chain.SenderAccount.GetAddress().String(),
	)

	res, err := chain.SendMsgs(msgTimeout)
	require.NoError(solo.t, err)
	require.NotNil(solo.t, res)
}

// GenerateSignature uses the stored private keys to generate a signature
// over the sign bytes with each key. If the amount of keys is greater than
// 1 then a multisig data type is returned.
func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte {
	sigs := make([]signing.SignatureData, len(solo.PrivateKeys))
	for i, key := range solo.PrivateKeys {
		sig, err := key.Sign(signBytes)
		require.NoError(solo.t, err)

		sigs[i] = &signing.SingleSignatureData{
			Signature: sig,
		}
	}

	var sigData signing.SignatureData
	if len(sigs) == 1 {
		// single public key
		sigData = sigs[0]
	} else {
		// generate multi signature data
		multiSigData := multisig.NewMultisig(len(sigs))
		for i, sig := range sigs {
			multisig.AddSignature(multiSigData, sig, i)
		}

		sigData = multiSigData
	}

	protoSigData := signing.SignatureDataToProto(sigData)
	bz, err := solo.cdc.Marshal(protoSigData)
	require.NoError(solo.t, err)

	return bz
}

// GenerateProof takes in solo machine sign bytes, generates a signature and marshals it as a proof.
// The solo machine sequence is incremented.
func (solo *Solomachine) GenerateProof(signBytes *solomachine.SignBytes) []byte {
	bz, err := solo.cdc.Marshal(signBytes)
	require.NoError(solo.t, err)

	sig := solo.GenerateSignature(bz)
	signatureDoc := &solomachine.TimestampedSignatureData{
		SignatureData: sig,
		Timestamp:     solo.Time,
	}
	proof, err := solo.cdc.Marshal(signatureDoc)
	require.NoError(solo.t, err)

	solo.Sequence++

	return proof
}

// GenerateConnOpenTryProof generates the proofTry required for the connection open ack handshake step.
// The clientID, connectionID provided represent the clientID and connectionID created on the counterparty chain, that is the tendermint chain.
func (solo *Solomachine) GenerateConnOpenTryProof(counterpartyClientID, counterpartyConnectionID string) []byte {
	counterparty := connectiontypes.NewCounterparty(counterpartyClientID, counterpartyConnectionID, prefix)
	connection := connectiontypes.NewConnectionEnd(connectiontypes.TRYOPEN, clientIDSolomachine, counterparty, []*connectiontypes.Version{ConnectionVersion}, DefaultDelayPeriod)

	data, err := solo.cdc.Marshal(&connection)
	require.NoError(solo.t, err)

	path := host.ConnectionKey(connectionIDSolomachine)
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        data,
	}

	return solo.GenerateProof(signBytes)
}

// GenerateChanOpenTryProof generates the proofTry required for the channel open ack handshake step.
// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain.
func (solo *Solomachine) GenerateChanOpenTryProof(portID, version, counterpartyChannelID string) []byte {
	counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID)
	channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version)

	data, err := solo.cdc.Marshal(&channel)
	require.NoError(solo.t, err)

	path := host.ChannelKey(portID, channelIDSolomachine)
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        data,
	}

	return solo.GenerateProof(signBytes)
}

// GenerateChanClosedProof generates a channel closed proof.
// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain.
func (solo *Solomachine) GenerateChanClosedProof(portID, version, counterpartyChannelID string) []byte {
	counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID)
	channel := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version)

	data, err := solo.cdc.Marshal(&channel)
	require.NoError(solo.t, err)

	path := host.ChannelKey(portID, channelIDSolomachine)
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        data,
	}

	return solo.GenerateProof(signBytes)
}

// GenerateCommitmentProof generates a commitment proof for the provided packet.
func (solo *Solomachine) GenerateCommitmentProof(packet channeltypes.Packet) []byte {
	commitment := channeltypes.CommitPacket(packet)

	path := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        commitment,
	}

	return solo.GenerateProof(signBytes)
}

// GenerateAcknowledgementProof generates an acknowledgement proof.
func (solo *Solomachine) GenerateAcknowledgementProof(packet channeltypes.Packet) []byte {
	transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement()

	path := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        channeltypes.CommitAcknowledgement(transferAck),
	}

	return solo.GenerateProof(signBytes)
}

// GenerateReceiptAbsenceProof generates a receipt absence proof for the provided packet.
func (solo *Solomachine) GenerateReceiptAbsenceProof(packet channeltypes.Packet) []byte {
	path := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
	signBytes := &solomachine.SignBytes{
		Sequence:    solo.Sequence,
		Timestamp:   solo.Time,
		Diversifier: solo.Diversifier,
		Path:        path,
		Data:        nil,
	}
	return solo.GenerateProof(signBytes)
}

// GetClientStatePath returns the commitment path for the client state.
func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypesv2.MerklePath {
	path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullClientStateKey(counterpartyClientIdentifier)))
	require.NoError(solo.t, err)

	return path
}

// GetConsensusStatePath returns the commitment path for the consensus state.
func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypesv2.MerklePath {
	path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullConsensusStateKey(counterpartyClientIdentifier, consensusHeight)))
	require.NoError(solo.t, err)

	return path
}

// GetConnectionStatePath returns the commitment path for the connection state.
func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypesv2.MerklePath {
	connectionPath := commitmenttypes.NewMerklePath(host.ConnectionKey(connID))
	path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath)
	require.NoError(solo.t, err)

	return path
}

// GetChannelStatePath returns the commitment path for that channel state.
func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypesv2.MerklePath {
	channelPath := commitmenttypes.NewMerklePath(host.ChannelKey(portID, channelID))
	path, err := commitmenttypes.ApplyPrefix(prefix, channelPath)
	require.NoError(solo.t, err)

	return path
}

// GetPacketCommitmentPath returns the commitment path for a packet commitment.
func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath {
	commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentKey(portID, channelID, sequence))
	path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath)
	require.NoError(solo.t, err)

	return path
}

// GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement.
func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath {
	ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(portID, channelID, sequence))
	path, err := commitmenttypes.ApplyPrefix(prefix, ackPath)
	require.NoError(solo.t, err)

	return path
}

// GetPacketReceiptPath returns the commitment path for a packet receipt
// and an absent receipts.
func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath {
	receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(portID, channelID, sequence))
	path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath)
	require.NoError(solo.t, err)

	return path
}

// GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter.
func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypesv2.MerklePath {
	nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvKey(portID, channelID))
	path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath)
	require.NoError(solo.t, err)

	return path
}
