package multisig

import (
	"golang.org/x/xerrors"

	"github.com/filecoin-project/go-address"
	"github.com/filecoin-project/go-state-types/abi"
	builtintypes "github.com/filecoin-project/go-state-types/builtin"
	builtin0 "github.com/filecoin-project/specs-actors/actors/builtin"
	init0 "github.com/filecoin-project/specs-actors/actors/builtin/init"
	multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig"

	"github.com/filecoin-project/lotus/chain/actors"
	init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init"
	"github.com/filecoin-project/lotus/chain/types"
)

type message0 struct{ from address.Address }

func (m message0) Create(
	signers []address.Address, threshold uint64,
	unlockStart, unlockDuration abi.ChainEpoch,
	initialAmount abi.TokenAmount,
) (*types.Message, error) {

	lenAddrs := uint64(len(signers))

	if lenAddrs < threshold {
		return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig")
	}

	if threshold == 0 {
		threshold = lenAddrs
	}

	if m.from == address.Undef {
		return nil, xerrors.Errorf("must provide source address")
	}

	if unlockStart != 0 {
		return nil, xerrors.Errorf("actors v0 does not support a non-zero vesting start time")
	}

	// Set up constructor parameters for multisig
	msigParams := &multisig0.ConstructorParams{
		Signers:               signers,
		NumApprovalsThreshold: threshold,
		UnlockDuration:        unlockDuration,
	}

	enc, actErr := actors.SerializeParams(msigParams)
	if actErr != nil {
		return nil, actErr
	}

	// new actors are created by invoking 'exec' on the init actor with the constructor params
	execParams := &init0.ExecParams{
		CodeCID:           builtin0.MultisigActorCodeID,
		ConstructorParams: enc,
	}

	enc, actErr = actors.SerializeParams(execParams)
	if actErr != nil {
		return nil, actErr
	}

	return &types.Message{
		To:     init_.Address,
		From:   m.from,
		Method: builtintypes.MethodsInit.Exec,
		Params: enc,
		Value:  initialAmount,
	}, nil
}

func (m message0) Propose(msig, to address.Address, amt abi.TokenAmount,
	method abi.MethodNum, params []byte) (*types.Message, error) {

	if msig == address.Undef {
		return nil, xerrors.Errorf("must provide a multisig address for proposal")
	}

	if to == address.Undef {
		return nil, xerrors.Errorf("must provide a target address for proposal")
	}

	if amt.Sign() == -1 {
		return nil, xerrors.Errorf("must provide a non-negative amount for proposed send")
	}

	if m.from == address.Undef {
		return nil, xerrors.Errorf("must provide source address")
	}

	enc, actErr := actors.SerializeParams(&multisig0.ProposeParams{
		To:     to,
		Value:  amt,
		Method: method,
		Params: params,
	})
	if actErr != nil {
		return nil, xerrors.Errorf("failed to serialize parameters: %w", actErr)
	}

	return &types.Message{
		To:     msig,
		From:   m.from,
		Value:  abi.NewTokenAmount(0),
		Method: builtin0.MethodsMultisig.Propose,
		Params: enc,
	}, nil
}

func (m message0) Approve(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) {
	enc, err := txnParams(txID, hashData)
	if err != nil {
		return nil, err
	}

	return &types.Message{
		To:     msig,
		From:   m.from,
		Value:  types.NewInt(0),
		Method: builtin0.MethodsMultisig.Approve,
		Params: enc,
	}, nil
}

func (m message0) Cancel(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) {
	enc, err := txnParams(txID, hashData)
	if err != nil {
		return nil, err
	}

	return &types.Message{
		To:     msig,
		From:   m.from,
		Value:  types.NewInt(0),
		Method: builtin0.MethodsMultisig.Cancel,
		Params: enc,
	}, nil
}
