package server

import (
	"time"

	"github.com/Tech-Gamer/nwy-wings/environment/docker"

	"github.com/Tech-Gamer/nwy-wings/environment"
)

// SyncWithEnvironment updates the environment for the server to match any of
// the changed data. This pushes new settings and environment variables to the
// environment. In addition, the in-situ update method is called on the
// environment which will allow environments that make use of it (such as Docker)
// to immediately apply some settings without having to wait on a server to
// restart.
//
// This functionality allows a server's resources limits to be modified on the
// fly and have them apply right away allowing for dynamic resource allocation
// and responses to abusive server processes.
func (s *Server) SyncWithEnvironment() {
	s.Log().Debug("syncing server settings with environment")

	cfg := s.Config()

	// Update the environment settings using the new information from this server.
	s.Environment.Config().SetSettings(environment.Settings{
		Mounts:      s.Mounts(),
		Allocations: cfg.Allocations,
		Limits:      cfg.Build,
	})

	// For Docker specific environments we also want to update the configured image
	// and stop configuration.
	if e, ok := s.Environment.(*docker.Environment); ok {
		s.Log().Debug("syncing stop configuration with configured docker environment")
		e.SetImage(cfg.Container.Image)
		e.SetStopConfiguration(s.ProcessConfiguration().Stop)
	}

	// If build limits are changed, environment variables also change. Plus, any modifications to
	// the startup command also need to be properly propagated to this environment.
	//
	// @see https://github.com/pterodactyl/panel/issues/2255
	s.Environment.Config().SetEnvironmentVariables(s.GetEnvironmentVariables())

	if !s.IsSuspended() {
		// Update the environment in place, allowing memory and CPU usage to be adjusted
		// on the fly without the user needing to reboot (theoretically).
		s.Log().Info("performing server limit modification on-the-fly")
		if err := s.Environment.InSituUpdate(); err != nil {
			// This is not a failure, the process is still running fine and will fix itself on the
			// next boot, or fail out entirely in a more logical position.
			s.Log().WithField("error", err).Warn("failed to perform on-the-fly update of the server environment")
		}
	} else {
		// Checks if the server is now in a suspended state. If so and a server process is currently running it
		// will be gracefully stopped (and terminated if it refuses to stop).
		if s.Environment.State() != environment.ProcessOfflineState {
			s.Log().Info("server suspended with running process state, terminating now")

			go func(s *Server) {
				if err := s.Environment.WaitForStop(s.Context(), time.Minute, true); err != nil {
					s.Log().WithField("error", err).Warn("failed to terminate server environment after suspension")
				}
			}(s)
		}
	}
}
