package registry

import (
	"context"
	"errors"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"go.signoz.io/signoz/pkg/factory"
	"go.uber.org/zap"
)

type Registry struct {
	services []factory.Service
	logger   *zap.Logger
	startCh  chan error
	stopCh   chan error
}

// New creates a new registry of services. It needs at least one service in the input.
func New(logger *zap.Logger, services ...factory.Service) (*Registry, error) {
	if logger == nil {
		return nil, fmt.Errorf("cannot build registry, logger is required")
	}

	if len(services) == 0 {
		return nil, fmt.Errorf("cannot build registry, at least one service is required")
	}

	return &Registry{
		logger:   logger.Named("go.signoz.io/pkg/registry"),
		services: services,
		startCh:  make(chan error, 1),
		stopCh:   make(chan error, len(services)),
	}, nil
}

func (r *Registry) Start(ctx context.Context) error {
	for _, s := range r.services {
		go func(s factory.Service) {
			err := s.Start(ctx)
			r.startCh <- err
		}(s)
	}

	return nil
}

func (r *Registry) Wait(ctx context.Context) error {
	interrupt := make(chan os.Signal, 1)
	signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)

	select {
	case <-ctx.Done():
		r.logger.Info("caught context error, exiting", zap.Any("context", ctx))
	case s := <-interrupt:
		r.logger.Info("caught interrupt signal, exiting", zap.Any("context", ctx), zap.Any("signal", s))
	case err := <-r.startCh:
		r.logger.Info("caught service error, exiting", zap.Any("context", ctx), zap.Error(err))
		return err
	}

	return nil
}

func (r *Registry) Stop(ctx context.Context) error {
	for _, s := range r.services {
		go func(s factory.Service) {
			err := s.Stop(ctx)
			r.stopCh <- err
		}(s)
	}

	errs := make([]error, len(r.services))
	for i := 0; i < len(r.services); i++ {
		err := <-r.stopCh
		if err != nil {
			errs = append(errs, err)
		}
	}

	return errors.Join(errs...)
}
