package heartbeats

import (
	"strconv"

	"github.com/gofiber/fiber/v2"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/guardian"
	"github.com/wormhole-foundation/wormhole-explorer/api/handlers/heartbeats"
	"github.com/wormhole-foundation/wormhole-explorer/api/response"
	"go.uber.org/zap"
)

// Controller definition.
type Controller struct {
	srv    *heartbeats.Service
	logger *zap.Logger
	gs     guardian.GuardianSet
}

// NewController create a new controler.
func NewController(srv *heartbeats.Service, logger *zap.Logger, p2pNetwork string) *Controller {
	return &Controller{
		srv:    srv,
		logger: logger.With(zap.String("module", "HeartbeatsController")),
		gs:     guardian.GetByEnv(p2pNetwork),
	}
}

// HeartbeatsResponse response.
type HeartbeatsResponse struct {
	Heartbeats []*HeartbeatResponse `json:"entries"`
}

type HeartbeatResponse struct {
	VerifiedGuardianAddr string        `json:"verifiedGuardianAddr"`
	P2PNodeAddr          string        `json:"p2pNodeAddr"`
	RawHeartbeat         *RawHeartbeat `json:"rawHeartbeat"`
}

type RawHeartbeat struct {
	NodeName      string                      `json:"nodeName"`
	Counter       string                      `json:"counter"`
	Timestamp     string                      `json:"timestamp"`
	Networks      []*HeartbeatNetworkResponse `json:"networks"`
	Version       string                      `json:"version"`
	GuardianAddr  string                      `json:"guardianAddr"`
	BootTimestamp string                      `json:"bootTimestamp"`
	Features      []string                    `json:"features"`
}

// HeartbeatNetwork definition.
type HeartbeatNetworkResponse struct {
	ID              int64  `bson:"id" json:"id"`
	Height          string `bson:"height" json:"height"`
	ContractAddress string `bson:"contractaddress" json:"contractAddress"`
	ErrorCount      string `bson:"errorcount" json:"errorCount"`
}

// GetGuardianSet godoc
// @Description Get heartbeats for guardians
// @Tags Guardian
// @ID guardians-hearbeats
// @Success 200 {object} HeartbeatsResponse
// @Failure 400
// @Failure 500
// @Router /v1/heartbeats [get]
func (c *Controller) GetLastHeartbeats(ctx *fiber.Ctx) error {
	// check guardianSet exists.
	if len(c.gs.GstByIndex) == 0 {
		return response.NewApiError(ctx, fiber.StatusServiceUnavailable, response.Unavailable,
			"guardian set not fetched from chain yet", nil)
	}
	// get lasted guardianSet.
	guardianSet := c.gs.GetLatest()
	guardianAddresses := guardianSet.KeysAsHexStrings()

	// get last heartbeats by ids.
	heartbeats, err := c.srv.GetHeartbeatsByIds(ctx.Context(), guardianAddresses)
	if err != nil {
		return err
	}

	// build heartbeats response compatible with grpc api response.
	response := buildHeartbeatResponse(heartbeats)
	return ctx.Status(fiber.StatusOK).JSON(response)
}

func buildHeartbeatResponse(heartbeats []*heartbeats.HeartbeatDoc) *HeartbeatsResponse {
	if heartbeats == nil {
		return nil
	}
	heartbeatResponses := make([]*HeartbeatResponse, 0, len(heartbeats))
	for _, heartbeat := range heartbeats {

		networkResponses := make([]*HeartbeatNetworkResponse, 0, len(heartbeat.Networks))
		for _, network := range heartbeat.Networks {
			networkResponse := &HeartbeatNetworkResponse{
				ID:              network.ID,
				Height:          strconv.Itoa(int(network.Height)),
				ContractAddress: network.ContractAddress,
				ErrorCount:      strconv.Itoa(int(network.ErrorCount)),
			}
			networkResponses = append(networkResponses, networkResponse)
		}

		hr := HeartbeatResponse{
			VerifiedGuardianAddr: heartbeat.ID,
			P2PNodeAddr:          "", // not exists in heartbeats mongo collection.
			RawHeartbeat: &RawHeartbeat{
				NodeName:      heartbeat.NodeName,
				Counter:       strconv.Itoa(int(heartbeat.Counter)),
				Timestamp:     strconv.Itoa(int(heartbeat.Timestamp)),
				Networks:      networkResponses,
				Version:       heartbeat.Version,
				GuardianAddr:  heartbeat.GuardianAddr,
				BootTimestamp: strconv.Itoa(int(heartbeat.BootTimestamp)),
				Features:      heartbeat.Features,
			},
		}
		heartbeatResponses = append(heartbeatResponses, &hr)
	}
	return &HeartbeatsResponse{
		Heartbeats: heartbeatResponses,
	}
}
