package ports

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 (
	// PortEventTopic is the used topic for port related entity changes.
	PortEventTopic = "port"
)

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

// PortService is a service for ports.
type PortService struct {
	store        Store
	eventService eventInterfaces.Service
}

// NewPortService creates a new PortService.
func NewPortService(store Store, eventService eventInterfaces.Service) Service {
	return &PortService{
		store:        store,
		eventService: eventService,
	}
}

// EnsureExists either creates a new port or returns an already existing port.
func (p *PortService) EnsureExists(port Port) (Port, error) {
	if port.ID == uuid.Nil {
		port.ID = uuid.New()
		return p.createPort(port)
	}

	existingPort, err := p.Get(query.Query{ID: port.ID})
	if err != nil {
		return p.createPort(port)
	}

	return existingPort, nil
}

func (p *PortService) createPort(port Port) (Port, error) {
	err := p.store.Add(port)
	if err != nil {
		return port, err
	}

	pubEvent := event.NewAddEvent(port.ID)
	if err := p.eventService.PublishEvent(PortEventTopic, pubEvent); err != nil {
		go func() {
			p.eventService.Reconnect()

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

	return port, nil
}

// Update updates an existing port.
func (p *PortService) Update(port Port) error {
	err := p.store.Update(port)
	if err != nil {
		return err
	}

	pubEvent := event.NewUpdateEvent(port.ID)
	if err := p.eventService.PublishEvent(PortEventTopic, pubEvent); err != nil {
		go func() {
			p.eventService.Reconnect()

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

	return nil
}

// Delete deletes a port.
func (p *PortService) Delete(port Port) error {
	err := p.store.Delete(port)
	if err != nil {
		return err
	}

	pubEvent := event.NewDeleteEvent(port.ID)
	if err := p.eventService.PublishEvent(PortEventTopic, pubEvent); err != nil {
		go func() {
			p.eventService.Reconnect()

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

	return nil
}

// Get gets a port.
func (p *PortService) Get(query query.Query) (Port, error) {
	port, err := p.store.Get(query)
	if err != nil {
		return port, err
	}

	return port, nil
}

// GetAll gets all existing ports.
func (p *PortService) GetAll() ([]Port, error) {
	nodes, err := p.store.GetAll()
	if err != nil {
		return nodes, err
	}
	return nodes, nil
}
