package simsx

import (
	"context"
	"errors"
	"fmt"
	"math/rand"

	"git.cw.tr/mukan-network/mukan-sdk/client"
	cryptotypes "git.cw.tr/mukan-network/mukan-sdk/crypto/types"
	"git.cw.tr/mukan-network/mukan-sdk/testutil/sims"
	sdk "git.cw.tr/mukan-network/mukan-sdk/types"
	simtypes "git.cw.tr/mukan-network/mukan-sdk/types/simulation"
)

type (
	// AppEntrypoint is the entrypoint to deliver sims TX to the system.
	AppEntrypoint interface {
		SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, *sdk.Result, error)
	}

	AccountSource interface {
		GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
	}
	// SimDeliveryResultHandler processes the delivery response error. Some sims are supposed to fail and expect an error.
	// An unhandled error returned indicates a failure
	SimDeliveryResultHandler func(error) error
)

// DeliverSimsMsg delivers a simulation message by creating and signing a mock transaction,
// then delivering it to the application through the specified entrypoint. It returns a legacy
// operation message representing the result of the delivery.
//
// The function takes the following parameters:
// - reporter: SimulationReporter - Interface for reporting the result of the delivery
// - r: *rand.Rand - Random number generator used for creating the mock transaction
// - app: AppEntrypoint - Entry point for delivering the simulation transaction to the application
// - txGen: client.TxConfig - Configuration for generating transactions
// - ak: AccountSource - Source for retrieving accounts
// - msg: sdk.Msg - The simulation message to be delivered
// - ctx: sdk.Context - The simulation context
// - chainID: string - The chain ID
// - senders: ...SimAccount - Accounts from which to send the simulation message
//
// The function returns a simtypes.OperationMsg, which is a legacy representation of the result
// of the delivery.
func DeliverSimsMsg(
	ctx context.Context,
	reporter SimulationReporter,
	app AppEntrypoint,
	r *rand.Rand,
	txGen client.TxConfig,
	ak AccountSource,
	chainID string,
	msg sdk.Msg,
	deliveryResultHandler SimDeliveryResultHandler,
	senders ...SimAccount,
) simtypes.OperationMsg {
	if reporter.IsSkipped() {
		return reporter.ToLegacyOperationMsg()
	}
	if len(senders) == 0 {
		reporter.Fail(errors.New("no senders"), "encoding TX")
		return reporter.ToLegacyOperationMsg()
	}
	accountNumbers := make([]uint64, len(senders))
	sequenceNumbers := make([]uint64, len(senders))
	for i := range senders {
		acc := ak.GetAccount(ctx, senders[i].Address)
		accountNumbers[i] = acc.GetAccountNumber()
		sequenceNumbers[i] = acc.GetSequence()
	}
	fees := senders[0].LiquidBalance().RandFees()
	tx, err := sims.GenSignedMockTx(
		r,
		txGen,
		[]sdk.Msg{msg},
		fees,
		sims.DefaultGenTxGas,
		chainID,
		accountNumbers,
		sequenceNumbers,
		Collect(senders, func(a SimAccount) cryptotypes.PrivKey { return a.PrivKey })...,
	)
	if err != nil {
		reporter.Fail(err, "encoding TX")
		return reporter.ToLegacyOperationMsg()
	}
	_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
	if err2 := deliveryResultHandler(err); err2 != nil {
		var comment string
		for _, msg := range tx.GetMsgs() {
			comment += fmt.Sprintf("%#v", msg)
		}
		reporter.Fail(err2, fmt.Sprintf("delivering tx with msgs: %s", comment))
		return reporter.ToLegacyOperationMsg()
	}
	reporter.Success(msg)
	return reporter.ToLegacyOperationMsg()
}
