package rpc

import (
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"

	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
	publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/governor"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/guardian"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/heartbeats"
	vaaservice "github.com/wormhole-foundation/wormhole-explorer/api/handlers/vaa"
	errs "github.com/wormhole-foundation/wormhole-explorer/api/internal/errors"
	"github.com/wormhole-foundation/wormhole-explorer/api/types"
	"github.com/wormhole-foundation/wormhole/sdk/vaa"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Handler rpc handler.
type Handler struct {
	publicrpcv1.UnimplementedPublicRPCServiceServer
	gs     guardian.GuardianSet
	vaaSrv *vaaservice.Service
	hbSrv  *heartbeats.Service
	govSrv *governor.Service
	logger *zap.Logger
}

// NewHandler create a new rpc Handler.
func NewHandler(vaaSrv *vaaservice.Service, hbSrv *heartbeats.Service, govSrv *governor.Service, logger *zap.Logger, p2pNetwork string) *Handler {
	return &Handler{gs: guardian.GetByEnv(p2pNetwork), vaaSrv: vaaSrv, hbSrv: hbSrv, govSrv: govSrv, logger: logger}
}

// GetSignedVAA get signedVAA by chainID, address, sequence.
func (h *Handler) GetSignedVAA(ctx context.Context, request *publicrpcv1.GetSignedVAARequest) (*publicrpcv1.GetSignedVAAResponse, error) {
	// check and get chainID/address/sequence
	if request.MessageId == nil {
		return nil, status.Error(codes.InvalidArgument, "no message ID specified")
	}

	chainID := vaa.ChainID(request.MessageId.EmitterChain.Number())

	// This interface is not supported for PythNet messages because those VAAs are not stored in the database.
	if chainID == vaa.ChainIDPythNet {
		return nil, status.Error(codes.InvalidArgument, "not supported for PythNet")
	}

	address, err := hex.DecodeString(request.MessageId.EmitterAddress)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address from hex: %v", err))
	}
	if len(address) != 32 {
		return nil, status.Error(codes.InvalidArgument, "address must be 32 bytes")
	}

	addr, err := types.BytesToAddress(address)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to decode address from bytes: %v", err))
	}

	sequence := strconv.FormatUint(request.MessageId.Sequence, 10)

	// get VAA by Id.
	vaa, err := h.vaaSrv.FindById(
		ctx,
		chainID,
		addr,
		sequence,
		false, /*includeParsedPayload*/
	)
	if err != nil {
		if errors.Is(err, errs.ErrNotFound) {
			return nil, status.Error(codes.NotFound, "requested VAA not found in store")
		}
		h.logger.Error("failed to fetch VAA", zap.Error(err), zap.Any("request", request))
		return nil, status.Error(codes.Internal, "internal server error")
	}

	// build GetSignedVAAResponse response.
	return &publicrpcv1.GetSignedVAAResponse{
		VaaBytes: vaa.Data.Vaa,
	}, nil
}

// GetSignedBatchVAA get signed batch VAA.
func (h *Handler) GetSignedBatchVAA(ctx context.Context, _ *publicrpcv1.GetSignedBatchVAARequest) (*publicrpcv1.GetSignedBatchVAAResponse, error) {
	return nil, status.Error(codes.Unimplemented, "not yet implemented")
}

// GetLastHeartbeats get last heartbeats.
func (h *Handler) GetLastHeartbeats(ctx context.Context, request *publicrpcv1.GetLastHeartbeatsRequest) (*publicrpcv1.GetLastHeartbeatsResponse, error) {
	// check guardianSet exists.
	if len(h.gs.GstByIndex) == 0 {
		return nil, status.Error(codes.Unavailable, "guardian set not fetched from chain yet")
	}

	// get lasted guardianSet.
	guardianSet := h.gs.GetLatest()
	guardianAddresses := guardianSet.KeysAsHexStrings()

	// get last heartbeats by ids.
	heartbeats, err := h.hbSrv.GetHeartbeatsByIds(ctx, guardianAddresses)
	if err != nil {
		return nil, status.Error(codes.Internal, "internal server error")
	}

	response := &publicrpcv1.GetLastHeartbeatsResponse{
		Entries: make([]*publicrpcv1.GetLastHeartbeatsResponse_Entry, 0),
	}

	for _, hb := range heartbeats {
		networkResponses := make([]*gossipv1.Heartbeat_Network, 0, len(hb.Networks))
		for _, network := range hb.Networks {
			networkResponse := gossipv1.Heartbeat_Network{
				Id:              uint32(network.ID),
				Height:          network.Height,
				ContractAddress: network.ContractAddress,
				ErrorCount:      uint64(network.ErrorCount), //TODO:check
			}
			networkResponses = append(networkResponses, &networkResponse)
		}

		rawHeartbeat := gossipv1.Heartbeat{
			Counter:       hb.Counter,
			NodeName:      hb.NodeName,
			Timestamp:     hb.Timestamp,
			Networks:      networkResponses,
			Version:       hb.Version,
			GuardianAddr:  hb.GuardianAddr,
			BootTimestamp: hb.BootTimestamp,
			Features:      hb.Features,
		}

		response.Entries = append(response.Entries, &publicrpcv1.GetLastHeartbeatsResponse_Entry{
			VerifiedGuardianAddr: hb.ID,
			P2PNodeAddr:          "",
			RawHeartbeat:         &rawHeartbeat,
		})
	}
	return response, nil
}

// GetCurrentGuardianSet get current guardian set.
func (h *Handler) GetCurrentGuardianSet(ctx context.Context, request *publicrpcv1.GetCurrentGuardianSetRequest) (*publicrpcv1.GetCurrentGuardianSetResponse, error) {
	// check guardianSet exists.
	if len(h.gs.GstByIndex) == 0 {
		return nil, status.Error(codes.Unavailable, "guardian set not fetched from chain yet")
	}
	// get lasted guardianSet.
	guardinSet := h.gs.GetLatest()

	// get guardian addresses.
	addresses := make([]string, len(guardinSet.Keys))
	for i, v := range guardinSet.Keys {
		addresses[i] = v.Hex()
	}

	return &publicrpcv1.GetCurrentGuardianSetResponse{
		GuardianSet: &publicrpcv1.GuardianSet{
			Index:     guardinSet.Index,
			Addresses: addresses,
		},
	}, nil
}

// GovernorGetAvailableNotionalByChain get availableNotional.
func (h *Handler) GovernorGetAvailableNotionalByChain(ctx context.Context, _ *publicrpcv1.GovernorGetAvailableNotionalByChainRequest) (*publicrpcv1.GovernorGetAvailableNotionalByChainResponse, error) {
	availableNotional, err := h.govSrv.GetAvailNotionByChain(ctx)
	if err != nil {
		return nil, err
	}
	entries := make([]*publicrpcv1.GovernorGetAvailableNotionalByChainResponse_Entry, 0)
	for _, v := range availableNotional {
		entry := publicrpcv1.GovernorGetAvailableNotionalByChainResponse_Entry{
			ChainId:                    uint32(v.ChainID),
			NotionalLimit:              uint64(v.NotionalLimit),
			RemainingAvailableNotional: uint64(v.AvailableNotional),
			BigTransactionSize:         uint64(v.MaxTransactionSize),
		}
		entries = append(entries, &entry)
	}
	response := publicrpcv1.GovernorGetAvailableNotionalByChainResponse{
		Entries: entries,
	}
	return &response, nil
}

// GovernorGetEnqueuedVAAs get enqueuedVaa.
func (h *Handler) GovernorGetEnqueuedVAAs(ctx context.Context, _ *publicrpcv1.GovernorGetEnqueuedVAAsRequest) (*publicrpcv1.GovernorGetEnqueuedVAAsResponse, error) {
	enqueuedVaa, err := h.govSrv.GetEnqueuedVaas(ctx)
	if err != nil {
		return nil, err
	}

	entries := make([]*publicrpcv1.GovernorGetEnqueuedVAAsResponse_Entry, 0, len(enqueuedVaa))
	for _, v := range enqueuedVaa {
		seqUint64, err := strconv.ParseUint(v.Sequence, 10, 64)
		if err != nil {
			return nil, err
		}
		entry := publicrpcv1.GovernorGetEnqueuedVAAsResponse_Entry{
			EmitterChain:   uint32(v.EmitterChain),
			EmitterAddress: v.EmitterAddress,
			Sequence:       seqUint64,
			ReleaseTime:    uint32(v.ReleaseTime),
			NotionalValue:  uint64(v.NotionalValue),
			TxHash:         v.TxHash,
		}
		entries = append(entries, &entry)
	}
	response := publicrpcv1.GovernorGetEnqueuedVAAsResponse{
		Entries: entries,
	}
	return &response, nil
}

// GovernorIsVAAEnqueued check if a vaa is enqueued.
func (h *Handler) GovernorIsVAAEnqueued(ctx context.Context, request *publicrpcv1.GovernorIsVAAEnqueuedRequest) (*publicrpcv1.GovernorIsVAAEnqueuedResponse, error) {
	if request.MessageId == nil {
		return nil, status.Error(codes.InvalidArgument, "Parameters are required")
	}
	chainID := vaa.ChainID(request.MessageId.EmitterChain)
	emitterAddress, err := types.StringToAddress(request.MessageId.EmitterAddress)
	if err != nil {
		return nil, status.Error(codes.InvalidArgument, "Invalid emitter address")
	}
	isEnqueued, err := h.govSrv.IsVaaEnqueued(ctx, chainID, emitterAddress, strconv.FormatUint(request.MessageId.Sequence, 10))
	if err != nil {
		return nil, err
	}
	return &publicrpcv1.GovernorIsVAAEnqueuedResponse{IsEnqueued: isEnqueued}, nil
}

// GovernorGetTokenList get governor token list.
func (h *Handler) GovernorGetTokenList(ctx context.Context, _ *publicrpcv1.GovernorGetTokenListRequest) (*publicrpcv1.GovernorGetTokenListResponse, error) {
	tokenList, err := h.govSrv.GetTokenList(ctx)
	if err != nil {
		return nil, err
	}

	entries := make([]*publicrpcv1.GovernorGetTokenListResponse_Entry, 0, len(tokenList))
	for _, t := range tokenList {
		entry := publicrpcv1.GovernorGetTokenListResponse_Entry{
			OriginChainId: uint32(t.OriginChainID),
			OriginAddress: t.OriginAddress,
			Price:         t.Price,
		}
		entries = append(entries, &entry)
	}

	response := publicrpcv1.GovernorGetTokenListResponse{
		Entries: entries,
	}

	return &response, nil
}
