package cmd

import (
	"io/fs"
	"os"
	"time"

	"github.com/AdguardTeam/AdGuardHome/internal/aghos"
	"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
	"github.com/AdguardTeam/AdGuardHome/internal/next/configmgr"
	"github.com/AdguardTeam/golibs/log"
)

// signalHandler processes incoming signals and shuts services down.
type signalHandler struct {
	// signal is the channel to which OS signals are sent.
	signal chan os.Signal

	// confFile is the path to the configuration file.
	confFile string

	// frontend is the filesystem with the frontend and other statically
	// compiled files.
	frontend fs.FS

	// start is the time at which AdGuard Home has been started.
	start time.Time

	// services are the services that are shut down before application exiting.
	services []agh.Service
}

// handle processes OS signals.
func (h *signalHandler) handle() {
	defer log.OnPanic("signalHandler.handle")

	for sig := range h.signal {
		log.Info("sighdlr: received signal %q", sig)

		if aghos.IsReconfigureSignal(sig) {
			h.reconfigure()
		} else if aghos.IsShutdownSignal(sig) {
			status := h.shutdown()
			log.Info("sighdlr: exiting with status %d", status)

			os.Exit(status)
		}
	}
}

// reconfigure rereads the configuration file and updates and restarts services.
func (h *signalHandler) reconfigure() {
	log.Info("sighdlr: reconfiguring adguard home")

	status := h.shutdown()
	if status != statusSuccess {
		log.Info("sighdlr: reconfiguring: exiting with status %d", status)

		os.Exit(status)
	}

	// TODO(a.garipov): This is a very rough way to do it.  Some services can be
	// reconfigured without the full shutdown, and the error handling is
	// currently not the best.

	confMgr, err := configmgr.New(h.confFile, h.frontend, h.start)
	check(err)

	web := confMgr.Web()
	err = web.Start()
	check(err)

	dns := confMgr.DNS()
	err = dns.Start()
	check(err)

	h.services = []agh.Service{
		dns,
		web,
	}

	log.Info("sighdlr: successfully reconfigured adguard home")
}

// Exit status constants.
const (
	statusSuccess = 0
	statusError   = 1
)

// shutdown gracefully shuts down all services.
func (h *signalHandler) shutdown() (status int) {
	ctx, cancel := ctxWithDefaultTimeout()
	defer cancel()

	status = statusSuccess

	log.Info("sighdlr: shutting down services")
	for i, service := range h.services {
		err := service.Shutdown(ctx)
		if err != nil {
			log.Error("sighdlr: shutting down service at index %d: %s", i, err)
			status = statusError
		}
	}

	return status
}

// newSignalHandler returns a new signalHandler that shuts down svcs.
func newSignalHandler(
	confFile string,
	frontend fs.FS,
	start time.Time,
	svcs ...agh.Service,
) (h *signalHandler) {
	h = &signalHandler{
		signal:   make(chan os.Signal, 1),
		confFile: confFile,
		frontend: frontend,
		start:    start,
		services: svcs,
	}

	aghos.NotifyShutdownSignal(h.signal)
	aghos.NotifyReconfigureSignal(h.signal)

	return h
}