package keeper

import (
	"bytes"
	"context"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"

	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/wormhole-foundation/wormchain/x/wormhole/types"
	wormholesdk "github.com/wormhole-foundation/wormhole/sdk"
)

// TODO(csongor): high-level overview of what this does
func (k msgServer) RegisterAccountAsGuardian(goCtx context.Context, msg *types.MsgRegisterAccountAsGuardian) (*types.MsgRegisterAccountAsGuardianResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	signer, err := sdk.AccAddressFromBech32(msg.Signer)
	if err != nil {
		return nil, err
	}
	// recover guardian key from signature
	signerHash := crypto.Keccak256Hash(wormholesdk.SignedWormchainAddressPrefix, signer)
	guardianKey, err := crypto.Ecrecover(signerHash.Bytes(), msg.Signature)

	if err != nil {
		return nil, err
	}

	// ecrecover gave us a 65-byte public key, which we first need to
	// convert to a 20 byte ethereum-style address. The first byte of the
	// public key is just the prefix byte '0x04' which we drop first. Then
	// hash the public key, and take the last 20 bytes of the hash
	// (according to
	// https://ethereum.org/en/developers/docs/accounts/#account-creation)
	guardianKeyAddr := common.BytesToAddress(crypto.Keccak256(guardianKey[1:])[12:])

	// next we check if this guardian key is in the most recent guardian set.
	// we don't allow registration of arbitrary public keys, since that would
	// enable a DoS vector
	latestGuardianSetIndex := k.Keeper.GetLatestGuardianSetIndex(ctx)
	consensusGuardianSetIndex, found := k.GetConsensusGuardianSetIndex(ctx)

	if found && latestGuardianSetIndex == consensusGuardianSetIndex.Index {
		return nil, types.ErrConsensusSetNotUpdatable
	}

	latestGuardianSet, found := k.Keeper.GetGuardianSet(ctx, latestGuardianSetIndex)

	if !found {
		return nil, types.ErrGuardianSetNotFound
	}

	if !latestGuardianSet.ContainsKey(guardianKeyAddr) {
		return nil, types.ErrGuardianNotFound
	}

	// Check if the tx signer was already registered as a guardian validator.
	for _, gv := range k.GetAllGuardianValidator(ctx) {
		if bytes.Equal(gv.ValidatorAddr, signer) {
			return nil, types.ErrSignerAlreadyRegistered
		}
	}

	// register validator in store for guardian
	k.Keeper.SetGuardianValidator(ctx, types.GuardianValidator{
		GuardianKey:   guardianKeyAddr.Bytes(),
		ValidatorAddr: signer,
	})

	err = ctx.EventManager().EmitTypedEvent(&types.EventGuardianRegistered{
		GuardianKey:  guardianKeyAddr.Bytes(),
		ValidatorKey: signer,
	})

	if err != nil {
		return nil, err
	}

	err = k.Keeper.TrySwitchToNewConsensusGuardianSet(ctx)

	if err != nil {
		return nil, err
	}

	return &types.MsgRegisterAccountAsGuardianResponse{}, nil
}
