package serverimp

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/pkg/api/routers/health"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

const defaultShutdownTimeout = 15 * time.Second

// Create returns a new server implementation configured with the given logger, config file, and debug flag.
func Create(logger mlogger.Logger, file string, debug bool) (*Imp, error) {
	return &Imp{
		logger: logger.Named("server"),
		file:   file,
		debug:  debug,
	}, nil
}

// Start loads configuration, starts metrics and the discovery registry, then blocks until stopped.
func (i *Imp) Start() error {
	i.initStopChannels()
	defer i.closeDone()

	i.logger.Info("Starting discovery service", zap.String("config_file", i.file), zap.Bool("debug", i.debug))

	cfg, err := i.loadConfig()
	if err != nil {
		return err
	}

	i.config = cfg

	messagingDriver := "none"
	if cfg.Messaging != nil {
		messagingDriver = string(cfg.Messaging.Driver)
	}

	metricsAddress := ""
	if cfg.Metrics != nil {
		metricsAddress = strings.TrimSpace(cfg.Metrics.Address)
	}

	if metricsAddress == "" {
		metricsAddress = "disabled"
	}

	i.logger.Info("Discovery config loaded",
		zap.String("messaging_driver", messagingDriver),
		zap.String("metrics_address", metricsAddress))

	i.startMetrics(cfg.Metrics)

	err = i.startDiscovery(cfg)
	if err != nil {
		i.stopDiscovery()
		i.setMetricsStatus(health.SSTerminating)
		ctx, cancel := context.WithTimeout(context.Background(), i.shutdownTimeout())
		i.shutdownMetrics(ctx)
		cancel()

		return err
	}

	i.setMetricsStatus(health.SSRunning)
	i.logger.Info("Discovery service ready", zap.String("messaging_driver", messagingDriver))

	<-i.stopCh
	i.logger.Info("Discovery service stop signal received")

	return nil
}

// Shutdown gracefully stops the discovery service and its metrics server.
func (i *Imp) Shutdown() {
	timeout := i.shutdownTimeout()
	i.logger.Info("Stopping discovery service", zap.Duration("timeout", timeout))

	i.stopDiscovery()
	i.signalStop()

	if i.doneCh != nil {
		<-i.doneCh
	}

	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	i.shutdownMetrics(ctx)
	cancel()

	i.logger.Info("Discovery service stopped")
}

func (i *Imp) initStopChannels() {
	if i.stopCh == nil {
		i.stopCh = make(chan struct{})
	}

	if i.doneCh == nil {
		i.doneCh = make(chan struct{})
	}
}

func (i *Imp) signalStop() {
	i.stopOnce.Do(func() {
		if i.stopCh != nil {
			close(i.stopCh)
		}
	})
}

func (i *Imp) closeDone() {
	i.doneOnce.Do(func() {
		if i.doneCh != nil {
			close(i.doneCh)
		}
	})
}

func (i *Imp) shutdownTimeout() time.Duration {
	if i.config != nil && i.config.Runtime != nil {
		return i.config.Runtime.ShutdownTimeout()
	}

	return defaultShutdownTimeout
}
