# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

"""
This module handles the reloading of service configuration files when they are changed.
in :mod:`config_files`, when a configuration file is changed, its base name is looked up
in this module's :data:`FILES_THAT_REQUIRE_RELOADS` map, and - if found - the function to
which it is mapped will be added to the :data:`NEEDED_RELOADS` set. Thus, to implement this
for a new service, all that need be done is to write a function to perform configuration
reloads, then add the names of configuration files to the map, pointing at the new function.
"""

import logging
import os
import subprocess
import typing

from functools import partial

import psutil

from .configuration import Configuration
from .utils import getYesNoResponse as getYN

#: Holds the list of reloads needed due to configuration file changes
NEEDED_RELOADS = set()

#: True if the host system has systemd D-Bus - actual value set at runtime
HAS_SYSTEMD = False

try:
	output = subprocess.check_output(["systemctl", "--no-pager"], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError:
	logging.debug("Host system does NOT have systemd - stack trace:", exc_info=True,stack_info=True)
else:
	HAS_SYSTEMD = True

def reloadATSConfigs(conf:Configuration) -> bool:
	"""
	This function will reload configuration files for the Apache Trafficserver caching HTTP
	proxy. It does this by calling ``traffic_ctl config reload`

	:param conf: An object representing the configuration of :program:`traffic_ops_ort`
	:returns: whether or not the reload succeeded (as indicated by the exit code of
		``traffic_ctl``)
	:raises OSError: when something goes wrong executing the child process
	"""
	# First of all, ATS must be running for this to work
	if not setATSStatus(True, conf):
		logging.error("Cannot reload configs, ATS not running!")
		return False

	cmd = [os.path.join(conf.tsroot, "bin", "traffic_ctl"), "config", "reload"]
	cmdStr = ' '.join(cmd)

	if ( conf.mode is Configuration.Modes.INTERACTIVE and
	     not getYN("Run command '%s' to reload configuration?" % cmdStr, default='Y')):
		logging.warning("Configuration will not be reloaded for Apache Trafficserver!")
		logging.warning("Changes will NOT be applied!")
		return True

	logging.info("Apache Trafficserver configuration reload will be done via: %s", cmdStr)

	if conf.mode is Configuration.Modes.REPORT:
		return True

	sub = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	out, err = sub.communicate()

	if sub.returncode:
		logging.debug("========== PROCESS STDOUT ==========")
		logging.debug("%s", out.decode())
		logging.debug("========== PROCESS STDERR ==========")
		logging.debug("%s", err.decode())
		logging.debug("====================================")
		return False
	return True

def restartATS(conf:Configuration) -> bool:
	"""
	A convenience function for calling :func:`setATSStatus` for restarts.

	:param conf: An object representing the configuration of :program:`traffic_ops_ort`
	:returns: whether or not the restart was successful (or unnecessary)
	"""

	doRestart = ( conf.mode is Configuration.Modes.BADASS or
	              conf.mode is Configuration.Modes.REPORT or
	              ( conf.mode is Configuration.Modes.INTERACTIVE and
	                getYN("Restart ATS?", default='Y')))

	return setATSStatus(True, conf, restart=doRestart)


def restartService(service:str, conf:Configuration) -> bool:
	"""
	Restarts a generic systemd service

	:param service: The name of the service to be restarted
	:param conf: An object representing the configuration of :program:`traffic_ops_ort`
	:returns: Whether or not the restart was successful
	"""
	global HAS_SYSTEMD

	if not HAS_SYSTEMD:
		logging.warning("This system doesn't have systemd, services cannot be restarted")
		return True

	if conf.mode is not Configuration.Modes.REPORT and (
	   conf.mode is not Configuration.Modes.INTERACTIVE or getYN("Restart %s?" % service, 'Y')):
		logging.info("Restarting %s", service)
		try:
			sub = subprocess.Popen(["systemctl", "restart", service],
			                       stdout=subprocess.PIPE,
			                       stderr=subprocess.PIPE)
			out, err = sub.communicate()
			logging.debug("stdout: %s\nstderr: %s", out, err)
		except (OSError, subprocess.CalledProcessError) as e: