package core

import (
	"fmt"
	"time"

	"github.com/nspcc-dev/neo-go/pkg/config"
	"github.com/nspcc-dev/neo-go/pkg/core/block"
	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
)

// CreateGenesisBlock creates a genesis block based on the given configuration.
func CreateGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) {
	validators, committee, err := validatorsFromConfig(cfg)
	if err != nil {
		return nil, err
	}

	nextConsensus, err := getNextConsensusAddress(validators)
	if err != nil {
		return nil, err
	}

	txs := []*transaction.Transaction{}
	if cfg.Genesis.Transaction != nil {
		committeeH, err := getCommitteeAddress(committee)
		if err != nil {
			return nil, fmt.Errorf("failed to calculate committee address: %w", err)
		}
		tx := cfg.Genesis.Transaction
		signers := []transaction.Signer{
			{
				Account: nextConsensus,
				Scopes:  transaction.CalledByEntry,
			},
		}
		scripts := []transaction.Witness{
			{
				InvocationScript:   []byte{},
				VerificationScript: []byte{byte(opcode.PUSH1)},
			},
		}
		if !committeeH.Equals(nextConsensus) {
			signers = append(signers, []transaction.Signer{
				{
					Account: committeeH,
					Scopes:  transaction.CalledByEntry,
				},
			}...)
			scripts = append(scripts, []transaction.Witness{
				{
					InvocationScript:   []byte{},
					VerificationScript: []byte{byte(opcode.PUSH1)},
				},
			}...)
		}

		txs = append(txs, &transaction.Transaction{
			SystemFee:       tx.SystemFee,
			ValidUntilBlock: 1,
			Script:          tx.Script,
			Signers:         signers,
			Scripts:         scripts,
		})
	}

	base := block.Header{
		Version:       0,
		PrevHash:      util.Uint256{},
		Timestamp:     uint64(time.Date(2016, 7, 15, 15, 8, 21, 0, time.UTC).Unix()) * 1000, // Milliseconds.
		Nonce:         2083236893,
		Index:         0,
		NextConsensus: nextConsensus,
		Script: transaction.Witness{
			InvocationScript:   []byte{},
			VerificationScript: []byte{byte(opcode.PUSH1)},
		},
		StateRootEnabled: cfg.StateRootInHeader,
	}

	b := &block.Block{
		Header:       base,
		Transactions: txs,
	}
	b.RebuildMerkleRoot()

	return b, nil
}

func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, []*keys.PublicKey, error) {
	vs, err := keys.NewPublicKeysFromStrings(cfg.StandbyCommittee)
	if err != nil {
		return nil, nil, err
	}
	return vs.Copy()[:cfg.GetNumOfCNs(0)], vs, nil
}

func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, err error) {
	raw, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators)
	if err != nil {
		return val, err
	}
	return hash.Hash160(raw), nil
}

func getCommitteeAddress(committee []*keys.PublicKey) (val util.Uint160, err error) {
	raw, err := smartcontract.CreateMajorityMultiSigRedeemScript(committee)
	if err != nil {
		return val, err
	}
	return hash.Hash160(raw), nil
}

// hashSliceReverse reverses the given slice of util.Uint256.
func hashSliceReverse(dest []util.Uint256) {
	for i, j := 0, len(dest)-1; i < j; i, j = i+1, j-1 {
		dest[i], dest[j] = dest[j], dest[i]
	}
}