package types

import (
	"errors"
	"fmt"
	"strings"

	"github.com/google/uuid"

	sdk "github.com/cosmos/cosmos-sdk/types"
)

// These TypeURL variables and values be generated by running unit test TestPrintMessageTypeStrings in msgs_test.go.
const (
	TypeURLMsgWriteScopeRequest                      = "/provenance.metadata.v1.MsgWriteScopeRequest"
	TypeURLMsgDeleteScopeRequest                     = "/provenance.metadata.v1.MsgDeleteScopeRequest"
	TypeURLMsgAddScopeDataAccessRequest              = "/provenance.metadata.v1.MsgAddScopeDataAccessRequest"
	TypeURLMsgDeleteScopeDataAccessRequest           = "/provenance.metadata.v1.MsgDeleteScopeDataAccessRequest"
	TypeURLMsgAddScopeOwnerRequest                   = "/provenance.metadata.v1.MsgAddScopeOwnerRequest"
	TypeURLMsgDeleteScopeOwnerRequest                = "/provenance.metadata.v1.MsgDeleteScopeOwnerRequest"
	TypeURLMsgUpdateValueOwnersRequest               = "/provenance.metadata.v1.MsgUpdateValueOwnersRequest"
	TypeURLMsgMigrateValueOwnerRequest               = "/provenance.metadata.v1.MsgMigrateValueOwnerRequest"
	TypeURLMsgWriteSessionRequest                    = "/provenance.metadata.v1.MsgWriteSessionRequest"
	TypeURLMsgWriteRecordRequest                     = "/provenance.metadata.v1.MsgWriteRecordRequest"
	TypeURLMsgDeleteRecordRequest                    = "/provenance.metadata.v1.MsgDeleteRecordRequest"
	TypeURLMsgWriteScopeSpecificationRequest         = "/provenance.metadata.v1.MsgWriteScopeSpecificationRequest"
	TypeURLMsgDeleteScopeSpecificationRequest        = "/provenance.metadata.v1.MsgDeleteScopeSpecificationRequest"
	TypeURLMsgWriteContractSpecificationRequest      = "/provenance.metadata.v1.MsgWriteContractSpecificationRequest"
	TypeURLMsgDeleteContractSpecificationRequest     = "/provenance.metadata.v1.MsgDeleteContractSpecificationRequest"
	TypeURLMsgAddContractSpecToScopeSpecRequest      = "/provenance.metadata.v1.MsgAddContractSpecToScopeSpecRequest"
	TypeURLMsgDeleteContractSpecFromScopeSpecRequest = "/provenance.metadata.v1.MsgDeleteContractSpecFromScopeSpecRequest"
	TypeURLMsgWriteRecordSpecificationRequest        = "/provenance.metadata.v1.MsgWriteRecordSpecificationRequest"
	TypeURLMsgDeleteRecordSpecificationRequest       = "/provenance.metadata.v1.MsgDeleteRecordSpecificationRequest"
	TypeURLMsgBindOSLocatorRequest                   = "/provenance.metadata.v1.MsgBindOSLocatorRequest"
	TypeURLMsgDeleteOSLocatorRequest                 = "/provenance.metadata.v1.MsgDeleteOSLocatorRequest"
	TypeURLMsgModifyOSLocatorRequest                 = "/provenance.metadata.v1.MsgModifyOSLocatorRequest"
	TypeURLMsgSetAccountDataRequest                  = "/provenance.metadata.v1.MsgSetAccountDataRequest"
)

// MetadataMsg extends the sdk.Msg interface with functions common to x/metadata messages.
type MetadataMsg interface {
	sdk.Msg

	// ValidateBasic runs stateless validation on a Msg.
	ValidateBasic() error
	// GetSignerStrs returns the bech32 address(es) that signed.
	GetSignerStrs() []string
}

// AllRequestMsgs defines all the Msg*Request messages.
var AllRequestMsgs = []MetadataMsg{
	(*MsgWriteScopeRequest)(nil),
	(*MsgDeleteScopeRequest)(nil),
	(*MsgAddScopeDataAccessRequest)(nil),
	(*MsgDeleteScopeDataAccessRequest)(nil),
	(*MsgAddScopeOwnerRequest)(nil),
	(*MsgDeleteScopeOwnerRequest)(nil),
	(*MsgUpdateValueOwnersRequest)(nil),
	(*MsgMigrateValueOwnerRequest)(nil),
	(*MsgWriteSessionRequest)(nil),
	(*MsgWriteRecordRequest)(nil),
	(*MsgDeleteRecordRequest)(nil),

	(*MsgWriteScopeSpecificationRequest)(nil),
	(*MsgDeleteScopeSpecificationRequest)(nil),
	(*MsgWriteContractSpecificationRequest)(nil),
	(*MsgDeleteContractSpecificationRequest)(nil),
	(*MsgAddContractSpecToScopeSpecRequest)(nil),
	(*MsgDeleteContractSpecFromScopeSpecRequest)(nil),
	(*MsgWriteRecordSpecificationRequest)(nil),
	(*MsgDeleteRecordSpecificationRequest)(nil),

	// omitting MsgWriteP8EContractSpecRequest and MsgP8EMemorializeContractRequest
	// since they're deprecated and no longer usable.

	(*MsgBindOSLocatorRequest)(nil),
	(*MsgDeleteOSLocatorRequest)(nil),
	(*MsgModifyOSLocatorRequest)(nil),

	(*MsgSetAccountDataRequest)(nil),

	(*MsgAddNetAssetValuesRequest)(nil),
}

// We still need these deprecated messages to be sdk.Msg for the codec.
var (
	_ sdk.Msg = (*MsgWriteP8EContractSpecRequest)(nil)
	_ sdk.Msg = (*MsgP8EMemorializeContractRequest)(nil)
)

// ------------------  MsgWriteScopeRequest  ------------------

// NewMsgWriteScopeRequest creates a new msg instance
func NewMsgWriteScopeRequest(scope Scope, signers []string, usdMills uint64) *MsgWriteScopeRequest {
	return &MsgWriteScopeRequest{
		Scope:    scope,
		Signers:  signers,
		UsdMills: usdMills,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteScopeRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteScopeRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	return msg.Scope.ValidateBasic()
}

// ConvertOptionalFields will look at the ScopeUuid and SpecUuid fields in the message.
// For each, if present, it will be converted to a MetadataAddress and set in the Scope appropriately.
// Once used, those uuid fields will be set to empty strings so that calling this again has no effect.
func (msg *MsgWriteScopeRequest) ConvertOptionalFields() error {
	if len(msg.ScopeUuid) > 0 {
		uid, err := uuid.Parse(msg.ScopeUuid)
		if err != nil {
			return fmt.Errorf("invalid scope uuid: %w", err)
		}
		scopeAddr := ScopeMetadataAddress(uid)
		if !msg.Scope.ScopeId.Empty() && !msg.Scope.ScopeId.Equals(scopeAddr) {
			return fmt.Errorf("msg.Scope.ScopeId [%s] is different from the one created from msg.ScopeUuid [%s]",
				msg.Scope.ScopeId, msg.ScopeUuid)
		}
		msg.Scope.ScopeId = scopeAddr
		msg.ScopeUuid = ""
	}
	if len(msg.SpecUuid) > 0 {
		uid, err := uuid.Parse(msg.SpecUuid)
		if err != nil {
			return fmt.Errorf("invalid spec uuid: %w", err)
		}
		specAddr := ScopeSpecMetadataAddress(uid)
		if !msg.Scope.SpecificationId.Empty() && !msg.Scope.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Scope.SpecificationId [%s] is different from the one created from msg.SpecUuid [%s]",
				msg.Scope.SpecificationId, msg.SpecUuid)
		}
		msg.Scope.SpecificationId = specAddr
		msg.SpecUuid = ""
	}
	return nil
}

func NewMsgWriteScopeResponse(scopeID MetadataAddress) *MsgWriteScopeResponse {
	return &MsgWriteScopeResponse{
		ScopeIdInfo: GetScopeIDInfo(scopeID),
	}
}

// ------------------  NewMsgDeleteScopeRequest  ------------------

// NewMsgDeleteScopeRequest creates a new msg instance
func NewMsgDeleteScopeRequest(scopeID MetadataAddress, signers []string) *MsgDeleteScopeRequest {
	return &MsgDeleteScopeRequest{
		ScopeId: scopeID,
		Signers: signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteScopeRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteScopeRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if !msg.ScopeId.IsScopeAddress() {
		return fmt.Errorf("invalid scope address")
	}
	return nil
}

// ------------------  MsgAddScopeDataAccessRequest  ------------------

// NewMsgAddScopeDataAccessRequest creates a new msg instance
func NewMsgAddScopeDataAccessRequest(scopeID MetadataAddress, dataAccessAddrs []string, signers []string) *MsgAddScopeDataAccessRequest {
	return &MsgAddScopeDataAccessRequest{
		ScopeId:    scopeID,
		DataAccess: dataAccessAddrs,
		Signers:    signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgAddScopeDataAccessRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgAddScopeDataAccessRequest) ValidateBasic() error {
	if !msg.ScopeId.IsScopeAddress() {
		return fmt.Errorf("address is not a scope id: %v", msg.ScopeId.String())
	}
	if len(msg.DataAccess) < 1 {
		return fmt.Errorf("at least one data access address is required")
	}
	for _, da := range msg.DataAccess {
		_, err := sdk.AccAddressFromBech32(da)
		if err != nil {
			return fmt.Errorf("data access address is invalid: %s", da)
		}
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgDeleteScopeDataAccessRequest  ------------------

// NewMsgDeleteScopeDataAccessRequest creates a new msg instance
func NewMsgDeleteScopeDataAccessRequest(scopeID MetadataAddress, dataAccessAddrs []string, signers []string) *MsgDeleteScopeDataAccessRequest {
	return &MsgDeleteScopeDataAccessRequest{
		ScopeId:    scopeID,
		DataAccess: dataAccessAddrs,
		Signers:    signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteScopeDataAccessRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteScopeDataAccessRequest) ValidateBasic() error {
	if !msg.ScopeId.IsScopeAddress() {
		return fmt.Errorf("address is not a scope id: %v", msg.ScopeId.String())
	}
	if len(msg.DataAccess) < 1 {
		return fmt.Errorf("at least one data access address is required")
	}
	for _, da := range msg.DataAccess {
		_, err := sdk.AccAddressFromBech32(da)
		if err != nil {
			return fmt.Errorf("data access address is invalid: %s", da)
		}
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgAddScopeOwnerRequest  ------------------

// NewMsgAddScopeOwnerRequest creates a new msg instance
func NewMsgAddScopeOwnerRequest(scopeID MetadataAddress, owners []Party, signers []string) *MsgAddScopeOwnerRequest {
	return &MsgAddScopeOwnerRequest{
		ScopeId: scopeID,
		Owners:  owners,
		Signers: signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgAddScopeOwnerRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgAddScopeOwnerRequest) ValidateBasic() error {
	if !msg.ScopeId.IsScopeAddress() {
		return fmt.Errorf("address is not a scope id: %v", msg.ScopeId.String())
	}
	if err := ValidatePartiesBasic(msg.Owners); err != nil {
		return fmt.Errorf("invalid owners: %w", err)
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgDeleteScopeOwnerRequest  ------------------

// NewMsgDeleteScopeOwnerRequest creates a new msg instance
func NewMsgDeleteScopeOwnerRequest(scopeID MetadataAddress, owners []string, signers []string) *MsgDeleteScopeOwnerRequest {
	return &MsgDeleteScopeOwnerRequest{
		ScopeId: scopeID,
		Owners:  owners,
		Signers: signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteScopeOwnerRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteScopeOwnerRequest) ValidateBasic() error {
	if !msg.ScopeId.IsScopeAddress() {
		return fmt.Errorf("address is not a scope id: %v", msg.ScopeId.String())
	}
	if len(msg.Owners) < 1 {
		return fmt.Errorf("at least one owner address is required")
	}
	for _, owner := range msg.Owners {
		_, err := sdk.AccAddressFromBech32(owner)
		if err != nil {
			return fmt.Errorf("owner address is invalid: %s", owner)
		}
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgUpdateValueOwnersRequest  ------------------

// NewMsgUpdateValueOwnersRequest creates a new msg instance
func NewMsgUpdateValueOwnersRequest(scopeIDs []MetadataAddress, valueOwner sdk.AccAddress, signers []string) *MsgUpdateValueOwnersRequest {
	return &MsgUpdateValueOwnersRequest{
		ScopeIds:          scopeIDs,
		ValueOwnerAddress: valueOwner.String(),
		Signers:           signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgUpdateValueOwnersRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgUpdateValueOwnersRequest) ValidateBasic() error {
	if len(msg.ScopeIds) == 0 {
		return fmt.Errorf("at least one scope id is required")
	}
	for i, scopeID := range msg.ScopeIds {
		if !scopeID.IsScopeAddress() {
			return fmt.Errorf("scope id[%d]: %q: invalid scope id", i, scopeID.String())
		}
	}

	_, err := sdk.AccAddressFromBech32(msg.ValueOwnerAddress)
	if err != nil {
		return fmt.Errorf("invalid value owner address: %w", err)
	}

	if len(msg.Signers) == 0 {
		return fmt.Errorf("at least one signer is required")
	}

	return nil
}

// ------------------  MsgMigrateValueOwnerRequest  ------------------

// NewMsgMigrateValueOwnerRequest creates a new msg instance
func NewMsgMigrateValueOwnerRequest(existing, proposed sdk.AccAddress, signers []string) *MsgMigrateValueOwnerRequest {
	return &MsgMigrateValueOwnerRequest{
		Existing: existing.String(),
		Proposed: proposed.String(),
		Signers:  signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgMigrateValueOwnerRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgMigrateValueOwnerRequest) ValidateBasic() error {
	_, err := sdk.AccAddressFromBech32(msg.Existing)
	if err != nil {
		return fmt.Errorf("invalid existing value owner address: %w", err)
	}
	_, err = sdk.AccAddressFromBech32(msg.Proposed)
	if err != nil {
		return fmt.Errorf("invalid proposed value owner address: %w", err)
	}

	if len(msg.Signers) == 0 {
		return fmt.Errorf("at least one signer is required")
	}

	return nil
}

// ------------------  MsgWriteSessionRequest  ------------------

// NewMsgWriteSessionRequest creates a new msg instance
func NewMsgWriteSessionRequest(session Session, signers []string) *MsgWriteSessionRequest {
	return &MsgWriteSessionRequest{Session: session, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteSessionRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteSessionRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	return msg.Session.ValidateBasic()
}

// ConvertOptionalFields will look at the SessionIdComponents and SpecUuid fields in the message.
// For each, if present, it will be converted to a MetadataAddress and set in the Session appropriately.
// Once used, those fields will be emptied so that calling this again has no effect.
func (msg *MsgWriteSessionRequest) ConvertOptionalFields() error {
	if msg.SessionIdComponents != nil {
		sessionAddr, err := msg.SessionIdComponents.GetSessionAddr()
		if err != nil {
			return fmt.Errorf("invalid session id components: %w", err)
		}
		if sessionAddr != nil {
			if !msg.Session.SessionId.Empty() && !msg.Session.SessionId.Equals(sessionAddr) {
				return fmt.Errorf("msg.Session.SessionId [%s] is different from the one created from msg.SessionIdComponents %v",
					msg.Session.SessionId, msg.SessionIdComponents)
			}
			msg.Session.SessionId = sessionAddr
		}
		msg.SessionIdComponents = nil
	}
	if len(msg.SpecUuid) > 0 {
		uid, err := uuid.Parse(msg.SpecUuid)
		if err != nil {
			return fmt.Errorf("invalid spec uuid: %w", err)
		}
		specAddr := ContractSpecMetadataAddress(uid)
		if !msg.Session.SpecificationId.Empty() && !msg.Session.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Session.SpecificationId [%s] is different from the one created from msg.SpecUuid [%s]",
				msg.Session.SpecificationId, msg.SpecUuid)
		}
		msg.Session.SpecificationId = specAddr
		msg.SpecUuid = ""
	}
	return nil
}

func NewMsgWriteSessionResponse(sessionID MetadataAddress) *MsgWriteSessionResponse {
	return &MsgWriteSessionResponse{
		SessionIdInfo: GetSessionIDInfo(sessionID),
	}
}

// ------------------  MsgWriteRecordRequest  ------------------

// NewMsgWriteRecordRequest creates a new msg instance
func NewMsgWriteRecordRequest(record Record, sessionIDComponents *SessionIdComponents, contractSpecUUID string, signers []string, parties []Party) *MsgWriteRecordRequest {
	return &MsgWriteRecordRequest{Record: record, Parties: parties, Signers: signers, SessionIdComponents: sessionIDComponents, ContractSpecUuid: contractSpecUUID}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteRecordRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteRecordRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	return msg.Record.ValidateBasic()
}

// ConvertOptionalFields will look at the SessionIdComponents and ContractSpecUuid fields in the message.
// For each, if present, it will be converted to a MetadataAddress and set in the Record appropriately.
// Once used, those fields will be emptied so that calling this again has no effect.
func (msg *MsgWriteRecordRequest) ConvertOptionalFields() error {
	if msg.SessionIdComponents != nil {
		sessionAddr, err := msg.SessionIdComponents.GetSessionAddr()
		if err != nil {
			return fmt.Errorf("invalid session id components: %w", err)
		}
		if sessionAddr != nil {
			if !msg.Record.SessionId.Empty() && !msg.Record.SessionId.Equals(sessionAddr) {
				return fmt.Errorf("msg.Record.SessionId [%s] is different from the one created from msg.SessionIdComponents %v",
					msg.Record.SessionId, msg.SessionIdComponents)
			}
			msg.Record.SessionId = sessionAddr
			msg.SessionIdComponents = nil
		}
	}
	if len(msg.ContractSpecUuid) > 0 {
		uid, err := uuid.Parse(msg.ContractSpecUuid)
		if err != nil {
			return fmt.Errorf("invalid contract spec uuid: %w", err)
		}
		if len(strings.TrimSpace(msg.Record.Name)) == 0 {
			return errors.New("empty record name")
		}
		specAddr := RecordSpecMetadataAddress(uid, msg.Record.Name)
		if !msg.Record.SpecificationId.Empty() && !msg.Record.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Record.SpecificationId [%s] is different from the one created from msg.ContractSpecUuid [%s] and msg.Record.Name [%s]",
				msg.Record.SpecificationId, msg.ContractSpecUuid, msg.Record.Name)
		}
		msg.Record.SpecificationId = specAddr
		msg.ContractSpecUuid = ""
	}
	return nil
}

func NewMsgWriteRecordResponse(recordID MetadataAddress) *MsgWriteRecordResponse {
	return &MsgWriteRecordResponse{
		RecordIdInfo: GetRecordIDInfo(recordID),
	}
}

// ------------------  MsgDeleteRecordRequest  ------------------

// NewMsgDeleteRecordRequest creates a new msg instance
func NewMsgDeleteRecordRequest(recordID MetadataAddress, signers []string) *MsgDeleteRecordRequest {
	return &MsgDeleteRecordRequest{RecordId: recordID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteRecordRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteRecordRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgWriteScopeSpecificationRequest  ------------------

// NewMsgWriteScopeSpecificationRequest creates a new msg instance
func NewMsgWriteScopeSpecificationRequest(specification ScopeSpecification, signers []string) *MsgWriteScopeSpecificationRequest {
	return &MsgWriteScopeSpecificationRequest{Specification: specification, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteScopeSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteScopeSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	if err := ValidateGenericPartyTypeUsage(msg.Specification.PartiesInvolved, msg.Specification.Description); err != nil {
		return err
	}

	return msg.Specification.ValidateBasic()
}

// ConvertOptionalFields will look at the SpecUuid field in the message.
// If present, it will be converted to a MetadataAddress and set in the Specification appropriately.
// Once used, it will be emptied so that calling this again has no effect.
func (msg *MsgWriteScopeSpecificationRequest) ConvertOptionalFields() error {
	if len(msg.SpecUuid) > 0 {
		uid, err := uuid.Parse(msg.SpecUuid)
		if err != nil {
			return fmt.Errorf("invalid spec uuid: %w", err)
		}
		specAddr := ScopeSpecMetadataAddress(uid)
		if !msg.Specification.SpecificationId.Empty() && !msg.Specification.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Specification.SpecificationId [%s] is different from the one created from msg.SpecUuid [%s]",
				msg.Specification.SpecificationId, msg.SpecUuid)
		}
		msg.Specification.SpecificationId = specAddr
		msg.SpecUuid = ""
	}
	return nil
}

func NewMsgWriteScopeSpecificationResponse(scopeSpecID MetadataAddress) *MsgWriteScopeSpecificationResponse {
	return &MsgWriteScopeSpecificationResponse{
		ScopeSpecIdInfo: GetScopeSpecIDInfo(scopeSpecID),
	}
}

// ------------------  MsgDeleteScopeSpecificationRequest  ------------------

// NewMsgDeleteScopeSpecificationRequest creates a new msg instance
func NewMsgDeleteScopeSpecificationRequest(specificationID MetadataAddress, signers []string) *MsgDeleteScopeSpecificationRequest {
	return &MsgDeleteScopeSpecificationRequest{SpecificationId: specificationID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteScopeSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteScopeSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgWriteContractSpecificationRequest  ------------------

// NewMsgWriteContractSpecificationRequest creates a new msg instance
func NewMsgWriteContractSpecificationRequest(specification ContractSpecification, signers []string) *MsgWriteContractSpecificationRequest {
	return &MsgWriteContractSpecificationRequest{Specification: specification, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteContractSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteContractSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	if err := ValidateGenericPartyTypeUsage(msg.Specification.PartiesInvolved, msg.Specification.Description); err != nil {
		return err
	}

	return msg.Specification.ValidateBasic()
}

// ConvertOptionalFields will look at the SpecUuid field in the message.
// If present, it will be converted to a MetadataAddress and set in the Specification appropriately.
// Once used, it will be emptied so that calling this again has no effect.
func (msg *MsgWriteContractSpecificationRequest) ConvertOptionalFields() error {
	if len(msg.SpecUuid) > 0 {
		uid, err := uuid.Parse(msg.SpecUuid)
		if err != nil {
			return fmt.Errorf("invalid spec uuid: %w", err)
		}
		specAddr := ContractSpecMetadataAddress(uid)
		if !msg.Specification.SpecificationId.Empty() && !msg.Specification.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Specification.SpecificationId [%s] is different from the one created from msg.SpecUuid [%s]",
				msg.Specification.SpecificationId, msg.SpecUuid)
		}
		msg.Specification.SpecificationId = specAddr
		msg.SpecUuid = ""
	}
	return nil
}

func NewMsgWriteContractSpecificationResponse(contractSpecID MetadataAddress) *MsgWriteContractSpecificationResponse {
	return &MsgWriteContractSpecificationResponse{
		ContractSpecIdInfo: GetContractSpecIDInfo(contractSpecID),
	}
}

// ------------------  MsgDeleteContractSpecificationRequest  ------------------

// NewMsgDeleteContractSpecificationRequest creates a new msg instance
func NewMsgDeleteContractSpecificationRequest(specificationID MetadataAddress, signers []string) *MsgDeleteContractSpecificationRequest {
	return &MsgDeleteContractSpecificationRequest{SpecificationId: specificationID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteContractSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteContractSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgAddContractSpecToScopeSpecRequest  ------------------

// NewMsgAddContractSpecToScopeSpecRequest creates a new msg instance
func NewMsgAddContractSpecToScopeSpecRequest(contractSpecID MetadataAddress, scopeSpecID MetadataAddress, signers []string) *MsgAddContractSpecToScopeSpecRequest {
	return &MsgAddContractSpecToScopeSpecRequest{ContractSpecificationId: contractSpecID, ScopeSpecificationId: scopeSpecID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgAddContractSpecToScopeSpecRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgAddContractSpecToScopeSpecRequest) ValidateBasic() error {
	if !msg.ContractSpecificationId.IsContractSpecificationAddress() {
		return fmt.Errorf("address is not a contract specification id: %s", msg.ContractSpecificationId.String())
	}
	if !msg.ScopeSpecificationId.IsScopeSpecificationAddress() {
		return fmt.Errorf("address is not a scope specification id: %s", msg.ScopeSpecificationId.String())
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgDeleteContractSpecFromScopeSpecRequest  ------------------

// NewMsgDeleteContractSpecFromScopeSpecRequest creates a new msg instance
func NewMsgDeleteContractSpecFromScopeSpecRequest(contractSpecID MetadataAddress, scopeSpecID MetadataAddress, signers []string) *MsgDeleteContractSpecFromScopeSpecRequest {
	return &MsgDeleteContractSpecFromScopeSpecRequest{ContractSpecificationId: contractSpecID, ScopeSpecificationId: scopeSpecID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteContractSpecFromScopeSpecRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteContractSpecFromScopeSpecRequest) ValidateBasic() error {
	if !msg.ContractSpecificationId.IsContractSpecificationAddress() {
		return fmt.Errorf("address is not a contract specification id: %s", msg.ContractSpecificationId.String())
	}
	if !msg.ScopeSpecificationId.IsScopeSpecificationAddress() {
		return fmt.Errorf("address is not a scope specification id: %s", msg.ScopeSpecificationId.String())
	}
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgWriteRecordSpecificationRequest  ------------------

// NewMsgWriteRecordSpecificationRequest creates a new msg instance
func NewMsgWriteRecordSpecificationRequest(recordSpecification RecordSpecification, signers []string) *MsgWriteRecordSpecificationRequest {
	return &MsgWriteRecordSpecificationRequest{Specification: recordSpecification, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgWriteRecordSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgWriteRecordSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	if err := msg.ConvertOptionalFields(); err != nil {
		return err
	}
	return msg.Specification.ValidateBasic()
}

// ConvertOptionalFields will look at the ContractSpecUuid field in the message.
// If present, it will be converted to a MetadataAddress and set in the Specification appropriately.
// Once used, it will be emptied so that calling this again has no effect.
func (msg *MsgWriteRecordSpecificationRequest) ConvertOptionalFields() error {
	if len(msg.ContractSpecUuid) > 0 {
		uid, err := uuid.Parse(msg.ContractSpecUuid)
		if err != nil {
			return fmt.Errorf("invalid spec uuid: %w", err)
		}
		if len(strings.TrimSpace(msg.Specification.Name)) == 0 {
			return errors.New("empty specification name")
		}
		specAddr := RecordSpecMetadataAddress(uid, msg.Specification.Name)
		if !msg.Specification.SpecificationId.Empty() && !msg.Specification.SpecificationId.Equals(specAddr) {
			return fmt.Errorf("msg.Specification.SpecificationId [%s] is different from the one created from msg.ContractSpecUuid [%s] and msg.Specification.Name [%s]",
				msg.Specification.SpecificationId, msg.ContractSpecUuid, msg.Specification.Name)
		}
		msg.Specification.SpecificationId = specAddr
		msg.ContractSpecUuid = ""
	}
	return nil
}

func NewMsgWriteRecordSpecificationResponse(recordSpecID MetadataAddress) *MsgWriteRecordSpecificationResponse {
	return &MsgWriteRecordSpecificationResponse{
		RecordSpecIdInfo: GetRecordSpecIDInfo(recordSpecID),
	}
}

// ------------------  MsgDeleteRecordSpecificationRequest  ------------------

// NewMsgDeleteRecordSpecificationRequest creates a new msg instance
func NewMsgDeleteRecordSpecificationRequest(specificationID MetadataAddress, signers []string) *MsgDeleteRecordSpecificationRequest {
	return &MsgDeleteRecordSpecificationRequest{SpecificationId: specificationID, Signers: signers}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteRecordSpecificationRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteRecordSpecificationRequest) ValidateBasic() error {
	if len(msg.Signers) < 1 {
		return fmt.Errorf("at least one signer is required")
	}
	return nil
}

// ------------------  MsgWriteP8EContractSpecRequest  ------------------

func (msg MsgWriteP8EContractSpecRequest) ValidateBasic() error {
	return errors.New("deprecated and unusable")
}

// ------------------  MsgP8EMemorializeContractRequest  ------------------

func (msg MsgP8EMemorializeContractRequest) ValidateBasic() error {
	return errors.New("deprecated and unusable")
}

// ------------------  MsgBindOSLocatorRequest  ------------------

// NewMsgBindOSLocatorRequest creates a new msg instance
func NewMsgBindOSLocatorRequest(obj ObjectStoreLocator) *MsgBindOSLocatorRequest {
	return &MsgBindOSLocatorRequest{
		Locator: obj,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgBindOSLocatorRequest) GetSignerStrs() []string {
	return []string{msg.Locator.Owner}
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgBindOSLocatorRequest) ValidateBasic() error {
	err := msg.Locator.Validate()
	if err != nil {
		return err
	}
	return nil
}

// ------------------  MsgDeleteOSLocatorRequest  ------------------

func NewMsgDeleteOSLocatorRequest(obj ObjectStoreLocator) *MsgDeleteOSLocatorRequest {
	return &MsgDeleteOSLocatorRequest{
		Locator: obj,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgDeleteOSLocatorRequest) GetSignerStrs() []string {
	return []string{msg.Locator.Owner}
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgDeleteOSLocatorRequest) ValidateBasic() error {
	err := msg.Locator.Validate()
	if err != nil {
		return err
	}

	return nil
}

// ------------------  MsgModifyOSLocatorRequest  ------------------

func NewMsgModifyOSLocatorRequest(obj ObjectStoreLocator) *MsgModifyOSLocatorRequest {
	return &MsgModifyOSLocatorRequest{
		Locator: obj,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgModifyOSLocatorRequest) GetSignerStrs() []string {
	return []string{msg.Locator.Owner}
}

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgModifyOSLocatorRequest) ValidateBasic() error {
	err := msg.Locator.Validate()
	if err != nil {
		return err
	}

	return nil
}

// ------------------  MsgSetAccountDataRequest  ------------------

// ValidateBasic performs as much validation as possible without outside info. Implements sdk.Msg interface.
func (msg MsgSetAccountDataRequest) ValidateBasic() error {
	if err := msg.MetadataAddr.Validate(); err != nil {
		return fmt.Errorf("invalid metadata address: %w", err)
	}
	if !msg.MetadataAddr.IsScopeAddress() {
		return fmt.Errorf("invalid metadata address: only scope ids are supported")
	}

	if len(msg.Signers) == 0 {
		return fmt.Errorf("at least one signer is required")
	}

	return nil
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgSetAccountDataRequest) GetSignerStrs() []string {
	return msg.Signers
}

// ------------------  MsgAddNetAssetValuesRequest  ------------------

func NewMsgAddNetAssetValuesRequest(scopeID string, signers []string, netAssetValues []NetAssetValue) *MsgAddNetAssetValuesRequest {
	return &MsgAddNetAssetValuesRequest{
		ScopeId:        scopeID,
		NetAssetValues: netAssetValues,
		Signers:        signers,
	}
}

// GetSignerStrs returns the bech32 address(es) that signed. Implements MetadataMsg interface.
func (msg MsgAddNetAssetValuesRequest) GetSignerStrs() []string {
	return msg.Signers
}

func (msg MsgAddNetAssetValuesRequest) ValidateBasic() error {
	if len(msg.NetAssetValues) == 0 {
		return fmt.Errorf("net asset value list cannot be empty")
	}

	scopeID, err := MetadataAddressFromBech32(msg.ScopeId)
	if err != nil {
		return fmt.Errorf("invalid metadata address %q: %w", msg.ScopeId, err)
	}
	if !scopeID.IsScopeAddress() {
		return fmt.Errorf("metadata address is not scope address: %v", scopeID.String())
	}

	seen := make(map[string]bool)
	for _, nav := range msg.NetAssetValues {
		if err := nav.Validate(); err != nil {
			return err
		}

		if nav.UpdatedBlockHeight != 0 {
			return fmt.Errorf("scope net asset value must not have update height set")
		}

		if seen[nav.Price.Denom] {
			return fmt.Errorf("list of net asset values contains duplicates")
		}
		seen[nav.Price.Denom] = true
	}

	for _, signer := range msg.Signers {
		_, err := sdk.AccAddressFromBech32(signer)
		if err != nil {
			return err
		}
	}

	return nil
}

// ------------------  SessionIdComponents  ------------------

func (msg *SessionIdComponents) GetSessionAddr() (MetadataAddress, error) {
	var scopeUUID, sessionUUID *uuid.UUID
	if len(msg.SessionUuid) > 0 {
		uid, err := uuid.Parse(msg.SessionUuid)
		if err != nil {
			return nil, fmt.Errorf("invalid session uuid: %w", err)
		}
		sessionUUID = &uid
	}
	if msgScopeUUID := msg.GetScopeUuid(); len(msgScopeUUID) > 0 {
		uid, err := uuid.Parse(msgScopeUUID)
		if err != nil {
			return nil, fmt.Errorf("invalid scope uuid: %w", err)
		}
		scopeUUID = &uid
	} else if msgScopeAddr := msg.GetScopeAddr(); len(msgScopeAddr) > 0 {
		addr, addrErr := MetadataAddressFromBech32(msgScopeAddr)
		if addrErr != nil {
			return nil, fmt.Errorf("invalid scope addr: %w", addrErr)
		}
		uid, err := addr.ScopeUUID()
		if err != nil {
			return nil, fmt.Errorf("invalid scope addr: %w", err)
		}
		scopeUUID = &uid
	}
	if scopeUUID == nil && sessionUUID == nil {
		return nil, nil
	}
	if scopeUUID == nil {
		return nil, errors.New("session uuid provided but missing scope uuid or addr")
	}
	if sessionUUID == nil {
		return nil, errors.New("scope uuid or addr provided but missing session uuid")
	}
	ma := SessionMetadataAddress(*scopeUUID, *sessionUUID)
	return ma, nil
}
