package shutdown

import (
	"context"
	"fmt"
	"sync"
	"time"

	"go.uber.org/zap"
)

// ShutdownManager gère l'arrêt gracieux de tous les services (BE-SVC-017)
type ShutdownManager struct {
	logger       *zap.Logger
	shutdowners  []Shutdowner
	mu           sync.Mutex
	shuttingDown bool
}

// Shutdowner est une interface pour les services qui peuvent être arrêtés proprement
type Shutdowner interface {
	Shutdown(ctx context.Context) error
	Name() string
}

// NewShutdownManager crée un nouveau gestionnaire de shutdown
func NewShutdownManager(logger *zap.Logger) *ShutdownManager {
	return &ShutdownManager{
		logger:       logger,
		shutdowners:  make([]Shutdowner, 0),
		shuttingDown: false,
	}
}

// Register enregistre un service pour l'arrêt gracieux
func (sm *ShutdownManager) Register(shutdowner Shutdowner) {
	sm.mu.Lock()
	defer sm.mu.Unlock()

	if sm.shuttingDown {
		sm.logger.Warn("Attempted to register shutdowner during shutdown",
			zap.String("name", shutdowner.Name()))
		return
	}

	sm.shutdowners = append(sm.shutdowners, shutdowner)
	sm.logger.Debug("Registered shutdowner",
		zap.String("name", shutdowner.Name()),
		zap.Int("total", len(sm.shutdowners)))
}

// Shutdown arrête tous les services enregistrés de manière gracieuse
func (sm *ShutdownManager) Shutdown(ctx context.Context) error {
	sm.mu.Lock()
	if sm.shuttingDown {
		sm.mu.Unlock()
		return fmt.Errorf("shutdown already in progress")
	}
	sm.shuttingDown = true
	shutdowners := make([]Shutdowner, len(sm.shutdowners))
	copy(shutdowners, sm.shutdowners)
	sm.mu.Unlock()

	sm.logger.Info("Starting graceful shutdown",
		zap.Int("services", len(shutdowners)))

	// Créer un contexte avec timeout global
	shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	// Arrêter tous les services en parallèle avec timeout individuel
	var wg sync.WaitGroup
	errors := make(chan error, len(shutdowners))

	for _, shutdowner := range shutdowners {
		wg.Add(1)
		go func(s Shutdowner) {
			defer wg.Done()

			// Créer un contexte avec timeout pour chaque service
			serviceCtx, serviceCancel := context.WithTimeout(shutdownCtx, 10*time.Second)
			defer serviceCancel()

			sm.logger.Info("Shutting down service",
				zap.String("service", s.Name()))

			start := time.Now()
			if err := s.Shutdown(serviceCtx); err != nil {
				sm.logger.Error("Error shutting down service",
					zap.String("service", s.Name()),
					zap.Error(err))
				errors <- fmt.Errorf("%s: %w", s.Name(), err)
			} else {
				duration := time.Since(start)
				sm.logger.Info("Service shut down successfully",
					zap.String("service", s.Name()),
					zap.Duration("duration", duration))
			}
		}(shutdowner)
	}

	// Attendre que tous les services soient arrêtés ou timeout
	done := make(chan struct{})
	go func() {
		wg.Wait()
		close(done)
	}()

	timeoutReached := false
	select {
	case <-done:
		sm.logger.Info("All services shut down")
	case <-shutdownCtx.Done():
		sm.logger.Warn("Shutdown timeout reached, some services may not have shut down cleanly")
		timeoutReached = true
	}

	// Collecter les erreurs
	close(errors)
	var shutdownErrors []error
	for err := range errors {
		shutdownErrors = append(shutdownErrors, err)
	}

	// Ajouter une erreur si le timeout est atteint
	if timeoutReached && len(shutdownErrors) == 0 {
		shutdownErrors = append(shutdownErrors, fmt.Errorf("shutdown timeout reached"))
	}

	if len(shutdownErrors) > 0 {
		return fmt.Errorf("shutdown completed with %d errors: %v", len(shutdownErrors), shutdownErrors)
	}

	return nil
}

// ShutdownFunc est une fonction helper pour créer un Shutdowner depuis une fonction
type ShutdownFunc struct {
	name string
	fn   func(ctx context.Context) error
}

// NewShutdownFunc crée un Shutdowner depuis une fonction
func NewShutdownFunc(name string, fn func(ctx context.Context) error) Shutdowner {
	return &ShutdownFunc{
		name: name,
		fn:   fn,
	}
}

// Shutdown exécute la fonction de shutdown
func (sf *ShutdownFunc) Shutdown(ctx context.Context) error {
	return sf.fn(ctx)
}

// Name retourne le nom du service
func (sf *ShutdownFunc) Name() string {
	return sf.name
}
