package nodes

import (
	"code.fbi.h-da.de/danet/gosdn/controller/event"
	eventInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/event"
	query "code.fbi.h-da.de/danet/gosdn/controller/store"

	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

const (
	// NodeEventTopic is the used topic for node related entity changes.
	NodeEventTopic = "node"
)

// Service defines a interface for a NodeService.
type Service interface {
	EnsureExists(Node) (Node, error)
	Update(Node) error
	Delete(Node) error
	Get(query.Query) (Node, error)
	GetAll() ([]Node, error)
}

// NodeService is a NodeService.
type NodeService struct {
	store        Store
	eventService eventInterfaces.Service
}

// NewNodeService creates a NodeService.
func NewNodeService(store Store, eventService eventInterfaces.Service) Service {
	return &NodeService{
		store:        store,
		eventService: eventService,
	}
}

// EnsureExists either creates a new node or returns an already existing node.
func (n *NodeService) EnsureExists(node Node) (Node, error) {
	if node.ID == uuid.Nil {
		node.ID = uuid.New()
		return n.createNode(node)
	}

	// Check if node with vaild UUID exists
	existingNode, err := n.Get(query.Query{ID: node.ID})
	if err != nil {
		// Create new node with that UUID
		return n.createNode(node)
	}

	return existingNode, nil
}

func (n *NodeService) createNode(node Node) (Node, error) {
	err := n.store.Add(node)
	if err != nil {
		return node, err
	}

	pubEvent := event.NewAddEvent(node.ID)
	if err := n.eventService.PublishEvent(NodeEventTopic, pubEvent); err != nil {
		go func() {
			n.eventService.Reconnect()

			retryErr := n.eventService.RetryPublish(NodeEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return node, nil
}

// Update updates an existing node.
func (n *NodeService) Update(node Node) error {
	err := n.store.Update(node)
	if err != nil {
		return err
	}

	pubEvent := event.NewUpdateEvent(node.ID)
	if err := n.eventService.PublishEvent(NodeEventTopic, pubEvent); err != nil {
		go func() {
			n.eventService.Reconnect()

			retryErr := n.eventService.RetryPublish(NodeEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Delete deletes a node.
func (n *NodeService) Delete(node Node) error {
	err := n.store.Delete(node)
	if err != nil {
		return err
	}

	pubEvent := event.NewDeleteEvent(node.ID)
	if err := n.eventService.PublishEvent(NodeEventTopic, pubEvent); err != nil {
		go func() {
			n.eventService.Reconnect()

			retryErr := n.eventService.RetryPublish(NodeEventTopic, pubEvent)
			if retryErr != nil {
				log.Error(retryErr)
			}
		}()
	}

	return nil
}

// Get gets a node.
func (n *NodeService) Get(query query.Query) (Node, error) {
	node, err := n.store.Get(query)
	if err != nil {
		return node, err
	}

	return node, nil
}

// GetAll gets all existing nodes.
func (n *NodeService) GetAll() ([]Node, error) {
	nodes, err := n.store.GetAll()
	if err != nil {
		return nodes, err
	}
	return nodes, nil
}
