package routingtables

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"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

const (
	// RoutingTableEventTopic is the used topic for routing table related entity changes.
	RoutingTableEventTopic = "routingTable"
)

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

// RoutingTableService is a RoutingTableService.
type RoutingTableService struct {
	store        Store
	nodeService  nodes.Service
	portService  ports.Service
	eventService eventInterfaces.Service
}

// NewRoutingTableService creates a RoutingTableService.
func NewRoutingTableService(
	store Store,
	nodeService nodes.Service,
	portService ports.Service,
	eventService eventInterfaces.Service,
) Service {
	return &RoutingTableService{
		store:        store,
		nodeService:  nodeService,
		portService:  portService,
		eventService: eventService,
	}
}

// EnsureExists either creates a new routingTable or returns an already existing routingTable.
func (r *RoutingTableService) EnsureExists(routingTable RoutingTable) (RoutingTable, error) {
	if routingTable.ID == uuid.Nil {
		routingTable.ID = uuid.New()
		return r.createRoutingTable(routingTable)
	}

	existingRoutingTable, err := r.Get(query.Query{ID: routingTable.ID})
	if err != nil {
		return r.createRoutingTable(routingTable)
	}

	return existingRoutingTable, nil
}

func (r *RoutingTableService) createRoutingTable(routingTable RoutingTable) (RoutingTable, error) {
	err := r.store.Add(routingTable)
	if err != nil {
		return routingTable, err
	}

	pubEvent := event.NewAddEvent(routingTable.ID)
	if err := r.eventService.PublishEvent(RoutingTableEventTopic, pubEvent); err != nil {
		go func() {
			r.eventService.Reconnect()

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

	return routingTable, nil
}

// Update updates an existing routingTable.
func (r *RoutingTableService) Update(routingTable RoutingTable) error {
	err := r.store.Update(routingTable)
	if err != nil {
		return err
	}

	pubEvent := event.NewUpdateEvent(routingTable.ID)
	if err := r.eventService.PublishEvent(RoutingTableEventTopic, pubEvent); err != nil {
		go func() {
			r.eventService.Reconnect()

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

	return nil
}

// Delete deletes a routingTable.
func (r *RoutingTableService) Delete(routingTable RoutingTable) error {
	err := r.store.Delete(routingTable)
	if err != nil {
		return err
	}

	pubEvent := event.NewDeleteEvent(routingTable.ID)
	if err := r.eventService.PublishEvent(RoutingTableEventTopic, pubEvent); err != nil {
		go func() {
			r.eventService.Reconnect()

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

	return nil
}

// Get gets a routingTable.
func (r *RoutingTableService) Get(query query.Query) (RoutingTable, error) {
	routingTable, err := r.store.Get(query)
	if err != nil {
		return routingTable, err
	}

	return routingTable, nil
}

// GetAll gets all existing routingTables.
func (r *RoutingTableService) GetAll() ([]RoutingTable, error) {
	nodes, err := r.store.GetAll()
	if err != nil {
		return nodes, err
	}
	return nodes, nil
}
