// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package nomad

import (
	"errors"
	"fmt"
	"strconv"

	"github.com/hashicorp/go-hclog"

	"github.com/hashicorp/nomad/nomad/structs"
)

// Status endpoint is used to check on server status
type Status struct {
	srv    *Server
	ctx    *RPCContext
	logger hclog.Logger
}

func NewStatusEndpoint(srv *Server, ctx *RPCContext) *Status {
	return &Status{srv: srv, ctx: ctx, logger: srv.logger.Named("status")}
}

// Ping is used to just check for connectivity
func (s *Status) Ping(args structs.GenericRequest, reply *struct{}) error {
	// note: we're intentionally throwing away any auth error here and only
	// authenticate so that we can measure rate metrics
	s.srv.Authenticate(s.ctx, &args)
	s.srv.MeasureRPCRate("status", structs.RateMetricRead, &args)
	return nil
}

// Leader is used to get the address of the leader
func (s *Status) Leader(args *structs.GenericRequest, reply *string) error {
	// note: we're intentionally throwing away any auth error here and only
	// authenticate so that we can measure rate metrics
	s.srv.Authenticate(s.ctx, args)
	s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)

	if args.Region == "" {
		args.Region = s.srv.config.Region
	}
	if done, err := s.srv.forward("Status.Leader", args, args, reply); done {
		return err
	}

	leader, _ := s.srv.raft.LeaderWithID()
	if leader != "" {
		*reply = string(leader)
	} else {
		*reply = ""
	}
	return nil
}

// Peers is used to get all the Raft peers
func (s *Status) Peers(args *structs.GenericRequest, reply *[]string) error {
	// note: we're intentionally throwing away any auth error here and only
	// authenticate so that we can measure rate metrics
	s.srv.Authenticate(s.ctx, args)
	s.srv.MeasureRPCRate("status", structs.RateMetricList, args)

	if args.Region == "" {
		args.Region = s.srv.config.Region
	}
	if done, err := s.srv.forward("Status.Peers", args, args, reply); done {
		return err
	}

	future := s.srv.raft.GetConfiguration()
	if err := future.Error(); err != nil {
		return err
	}

	for _, server := range future.Configuration().Servers {
		*reply = append(*reply, string(server.Address))
	}
	return nil
}

// Members return the list of servers in a cluster that a particular server is
// aware of
func (s *Status) Members(args *structs.GenericRequest, reply *structs.ServerMembersResponse) error {
	authErr := s.srv.Authenticate(s.ctx, args)
	s.srv.MeasureRPCRate("status", structs.RateMetricList, args)
	if authErr != nil {
		return structs.ErrPermissionDenied
	}
	// Check node read permissions
	if aclObj, err := s.srv.ResolveACL(args); err != nil {
		return err
	} else if aclObj != nil && !aclObj.AllowNodeRead() {
		return structs.ErrPermissionDenied
	}

	serfMembers := s.srv.Members()
	members := make([]*structs.ServerMember, len(serfMembers))
	for i, mem := range serfMembers {
		members[i] = &structs.ServerMember{
			Name:        mem.Name,
			Addr:        mem.Addr,
			Port:        mem.Port,
			Tags:        mem.Tags,
			Status:      mem.Status.String(),
			ProtocolMin: mem.ProtocolMin,
			ProtocolMax: mem.ProtocolMax,
			ProtocolCur: mem.ProtocolCur,
			DelegateMin: mem.DelegateMin,
			DelegateMax: mem.DelegateMax,
			DelegateCur: mem.DelegateCur,
		}
	}
	*reply = structs.ServerMembersResponse{
		ServerName:   s.srv.config.NodeName,
		ServerRegion: s.srv.config.Region,
		ServerDC:     s.srv.config.Datacenter,
		Members:      members,
	}
	return nil
}

// RaftStats is used by Autopilot to query the raft stats of the local server.
func (s *Status) RaftStats(args *structs.GenericRequest, reply *structs.RaftStats) error {
	// note: we're intentionally throwing away any auth error here and only
	// authenticate so that we can measure rate metrics
	s.srv.Authenticate(s.ctx, args)
	s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)

	stats := s.srv.raft.Stats()

	var err error
	reply.LastContact = stats["last_contact"]
	reply.LastIndex, err = strconv.ParseUint(stats["last_log_index"], 10, 64)
	if err != nil {
		return fmt.Errorf("error parsing server's last_log_index value: %s", err)
	}
	reply.LastTerm, err = strconv.ParseUint(stats["last_log_term"], 10, 64)
	if err != nil {
		return fmt.Errorf("error parsing server's last_log_term value: %s", err)
	}

	return nil
}

// HasNodeConn returns whether the server has a connection to the requested
// Node.
func (s *Status) HasNodeConn(args *structs.NodeSpecificRequest, reply *structs.NodeConnQueryResponse) error {
	// note: we're intentionally throwing away any auth error here and only
	// authenticate so that we can measure rate metrics
	s.srv.Authenticate(s.ctx, args)
	s.srv.MeasureRPCRate("status", structs.RateMetricRead, args)

	// Validate the args
	if args.NodeID == "" {
		return errors.New("Must provide the NodeID")
	}

	state, ok := s.srv.getNodeConn(args.NodeID)
	if ok {
		reply.Connected = true
		reply.Established = state.Established
	}

	return nil
}
