package home

import (
	"context"
	"log/slog"
	"os"
	"sync"
	"sync/atomic"
	"syscall"

	"github.com/AdguardTeam/AdGuardHome/internal/client"
	"github.com/AdguardTeam/golibs/logutil/slogutil"
	"github.com/AdguardTeam/golibs/osutil"
)

// signalHandler processes incoming signals.  It reloads configurations of
// stored entities on SIGHUP and performs cleanup on all other signals.
type signalHandler struct {
	// logger is used to log the operation of the signal handler.  Initially,
	// [slog.Default] is used, but it should be swapped later using
	// [signalHandler.swapLogger].
	logger *atomic.Pointer[slog.Logger]

	// mu protects clientStorage and tlsManager.
	mu *sync.Mutex

	// clientStorage is used to reload information about runtime clients with an
	// ARP source.
	clientStorage *client.Storage

	// tlsManager is used to reload the TLS configuration.
	tlsManager *tlsManager

	// signals receives incoming signals.
	signals <-chan os.Signal

	// cleanup is called to perform cleanup on all incoming signals, except
	// SIGHUP.
	cleanup func(ctx context.Context)
}

// newSignalHandler returns a new properly initialized *signalHandler.
func newSignalHandler(
	signals <-chan os.Signal,
	cleanup func(ctx context.Context),
) (h *signalHandler) {
	h = &signalHandler{
		logger:  &atomic.Pointer[slog.Logger]{},
		mu:      &sync.Mutex{},
		signals: signals,
		cleanup: cleanup,
	}

	h.logger.Store(slog.Default())

	return h
}

// swapLogger replaces the stored logger with the given logger.
func (h *signalHandler) swapLogger(logger *slog.Logger) {
	h.logger.Swap(logger)
}

// addClientStorage stores the client storage.
func (h *signalHandler) addClientStorage(s *client.Storage) {
	h.mu.Lock()
	defer h.mu.Unlock()

	h.clientStorage = s
}

// addTLSManager stores the TLS manager.
func (h *signalHandler) addTLSManager(m *tlsManager) {
	h.mu.Lock()
	defer h.mu.Unlock()

	h.tlsManager = m
}

// handle processes incoming signals.  It blocks until a signal is received.  It
// reloads configurations of stored entities on SIGHUP, or performs cleanup on
// all other signals.  It is intended to be used as a goroutine.
func (h *signalHandler) handle(ctx context.Context) {
	// NOTE:  Avoid using [slogutil.RecoverAndExit] to prevent immediate
	// evaluation of the logger.
	defer func() {
		v := recover()
		if v == nil {
			return
		}

		slogutil.PrintRecovered(ctx, h.logger.Load(), v)

		os.Exit(osutil.ExitCodeFailure)
	}()

	for {
		sig := <-h.signals
		h.logger.Load().InfoContext(ctx, "received signal", "signal", sig)
		switch sig {
		case syscall.SIGHUP:
			h.reloadConfig(ctx)
		default:
			h.cleanup(ctx)
		}
	}
}

// reloadConfig refreshes configurations of stored entities.
func (h *signalHandler) reloadConfig(ctx context.Context) {
	h.mu.Lock()
	defer h.mu.Unlock()

	if h.clientStorage != nil {
		h.clientStorage.ReloadARP(ctx)
	}

	if h.tlsManager != nil {
		h.tlsManager.reload(ctx)
	}
}