package nmon

import (
	"slices"

	"github.com/opensvc/om3/v3/core/cluster"
	"github.com/opensvc/om3/v3/core/keyop"
	"github.com/opensvc/om3/v3/core/object"
	"github.com/opensvc/om3/v3/daemon/msgbus"
	"github.com/opensvc/om3/v3/util/hostname"
	"github.com/opensvc/om3/v3/util/key"
	"github.com/opensvc/om3/v3/util/pubsub"
)

// onJoinRequest handle JoinRequest to update cluster config with new node.
//
// If error occurs publish msgbus.JoinIgnored, or msgbus.JoinError:
//
// - publish msgbus.JoinIgnored,candidate_node=node (the node already exists in cluster nodes)
// - publish msgbus.JoinError,candidate_node=node (update cluster config object fails)
func (t *Manager) onJoinRequest(c *msgbus.JoinRequest) {
	nodes := cluster.ConfigData.Get().Nodes
	candidate := c.CandidateNode
	labels := []pubsub.Label{
		{"node", hostname.Hostname()},
		{"candidate_node", candidate},
	}
	t.log.Infof("join request from candidate node %s", candidate)
	if slices.Contains(nodes, candidate) {
		t.log.Tracef("join request ignored already member")
		t.publisher.Pub(&msgbus.JoinIgnored{CandidateNode: candidate}, labels...)
	} else if err := t.addClusterNode(candidate); err != nil {
		t.log.Warnf("join request denied: %s", err)
		t.publisher.Pub(&msgbus.JoinError{CandidateNode: candidate, Reason: err.Error()}, labels...)
	}
}

// addClusterNode adds node to cluster config
func (t *Manager) addClusterNode(node string) error {
	t.log.Tracef("adding cluster node %s", node)
	ccfg, err := object.NewCluster(object.WithVolatile(false))
	if err != nil {
		return err
	}
	op := keyop.New(key.New("cluster", "nodes"), keyop.Append, node, 0)
	if err := ccfg.Config().Set(*op); err != nil {
		return err
	}
	if err != nil {
		return err
	}
	return nil
}

// onLeaveRequest handle LeaveRequest to update cluster config with node removal.
//
// If an error occurs, publish msgbus.LeaveIgnored, or msgbus.LeaveError:
//
// - publish msgbus.LeaveIgnored,candidate_node=node (the node is not a cluster node)
// - publish msgbus.LeaveError,candidate_node=node (update cluster config object fails)
func (t *Manager) onLeaveRequest(c *msgbus.LeaveRequest) {
	nodes := cluster.ConfigData.Get().Nodes
	candidate := c.CandidateNode
	labels := []pubsub.Label{
		{"node", hostname.Hostname()},
		{"candidate_node", candidate},
	}
	t.log.Infof("leave request for candidate node %s", candidate)
	if !slices.Contains(nodes, candidate) {
		t.log.Tracef("leave request ignored for not cluster member")
		t.publisher.Pub(&msgbus.LeaveIgnored{CandidateNode: candidate}, labels...)
	} else if err := t.removeClusterNode(candidate); err != nil {
		t.log.Warnf("leave request denied: %s", err)
		t.publisher.Pub(&msgbus.LeaveError{CandidateNode: candidate, Reason: err.Error()}, labels...)
	}
}

// removeClusterNode removes node from cluster config
func (t *Manager) removeClusterNode(node string) error {
	t.log.Tracef("removing cluster node %s", node)
	ccfg, err := object.NewCluster(object.WithVolatile(false))
	if err != nil {
		return err
	}
	op := keyop.New(key.New("cluster", "nodes"), keyop.Remove, node, 0)
	if err := ccfg.Config().Set(*op); err != nil {
		return err
	}
	if err != nil {
		return err
	}
	return nil
}
