package raft

import (
	"errors"

	"github.com/coreos/etcd/pkg/types"
)

type RaftNodeInfo struct {
	ClusterSize    int        `json:"clusterSize"`
	Role           string     `json:"role"`
	Address        *Address   `json:"address"`
	PeerAddresses  []*Address `json:"peerAddresses"`
	RemovedPeerIds []uint16   `json:"removedPeerIds"`
	AppliedIndex   uint64     `json:"appliedIndex"`
	SnapshotIndex  uint64     `json:"snapshotIndex"`
}

type PublicRaftAPI struct {
	raftService *RaftService
}

func NewPublicRaftAPI(raftService *RaftService) *PublicRaftAPI {
	return &PublicRaftAPI{raftService}
}

func (s *PublicRaftAPI) Role() string {
	if err := s.checkIfNodeInCluster(); err != nil {
		return ""
	}
	_, err := s.raftService.raftProtocolManager.LeaderAddress()
	if err != nil {
		return ""
	}
	return s.raftService.raftProtocolManager.NodeInfo().Role
}

// helper function to check if self node is part of cluster
func (s *PublicRaftAPI) checkIfNodeInCluster() error {
	if s.raftService.raftProtocolManager.IsIDRemoved(uint64(s.raftService.raftProtocolManager.raftId)) {
		return errors.New("node not part of raft cluster. operations not allowed")
	}
	return nil
}

func (s *PublicRaftAPI) AddPeer(enodeId string) (uint16, error) {
	if err := s.checkIfNodeInCluster(); err != nil {
		return 0, err
	}
	return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, false)
}

func (s *PublicRaftAPI) AddLearner(enodeId string) (uint16, error) {
	if err := s.checkIfNodeInCluster(); err != nil {
		return 0, err
	}
	return s.raftService.raftProtocolManager.ProposeNewPeer(enodeId, true)
}

func (s *PublicRaftAPI) PromoteToPeer(raftId uint16) (bool, error) {
	if err := s.checkIfNodeInCluster(); err != nil {
		return false, err
	}
	return s.raftService.raftProtocolManager.PromoteToPeer(raftId)
}

func (s *PublicRaftAPI) RemovePeer(raftId uint16) error {
	if err := s.checkIfNodeInCluster(); err != nil {
		return err
	}
	return s.raftService.raftProtocolManager.ProposePeerRemoval(raftId)
}

func (s *PublicRaftAPI) Leader() (string, error) {
	addr, err := s.raftService.raftProtocolManager.LeaderAddress()
	if err != nil {
		return "", err
	}
	return addr.NodeId.String(), nil
}

func (s *PublicRaftAPI) Cluster() ([]ClusterInfo, error) {
	// check if the node has already been removed from cluster
	// if yes return nil
	if err := s.checkIfNodeInCluster(); err != nil {
		return []ClusterInfo{}, nil
	}

	nodeInfo := s.raftService.raftProtocolManager.NodeInfo()
	if nodeInfo.Role == "" {
		return []ClusterInfo{}, nil
	}

	noLeader := false
	leaderAddr, err := s.raftService.raftProtocolManager.LeaderAddress()
	if err != nil {
		noLeader = true
		if s.raftService.raftProtocolManager.NodeInfo().Role == "" {
			return []ClusterInfo{}, nil
		}
	}

	peerAddresses := append(nodeInfo.PeerAddresses, nodeInfo.Address)
	clustInfo := make([]ClusterInfo, len(peerAddresses))
	for i, a := range peerAddresses {
		role := ""
		if !noLeader {
			if a.RaftId == leaderAddr.RaftId {
				role = "minter"
			} else if s.raftService.raftProtocolManager.isLearner(a.RaftId) {
				role = "learner"
			} else if s.raftService.raftProtocolManager.isVerifier(a.RaftId) {
				role = "verifier"
			}
		}
		clustInfo[i] = ClusterInfo{*a, role, s.checkIfNodeIsActive(a.RaftId)}
	}
	return clustInfo, nil
}

// checkIfNodeIsActive checks if the raft node is active
// if the raft node is active ActiveSince returns non-zero time
func (s *PublicRaftAPI) checkIfNodeIsActive(raftId uint16) bool {
	if raftId == s.raftService.raftProtocolManager.raftId {
		return true
	}
	activeSince := s.raftService.raftProtocolManager.transport.ActiveSince(types.ID(raftId))
	return !activeSince.IsZero()
}

func (s *PublicRaftAPI) GetRaftId(enodeId string) (uint16, error) {
	return s.raftService.raftProtocolManager.FetchRaftId(enodeId)
}
