# 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 deals with the management of configuration files,
presumably for a cache server
"""

import logging
import os
import re
import typing

from base64 import b64decode

from trafficops.restapi import OperationError, InvalidJSONError, LoginError

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


#: Holds a set of service names that need reloaded configs, mapped to a boolean which indicates
#: whether (:const:`True`) or not (:const:`False`) a full restart is required.
RELOADS_REQUIRED = set()

#: A constant that holds the absolute path to the backup directory for configuration files
BACKUP_DIR = "/opt/ort/backups"

#: a pre-compiled regular expression to use in parsing
SSL_KEY_REGEX = re.compile(r'^\s*ssl_cert_name\=(.*)\s+ssl_key_name\=(.*)\s*$')

class ConfigurationError(Exception):
	"""
	Represents an error updating configuration files
	"""
	pass

class ConfigFile():
	"""
	Represents a configuration file on a host system.
	"""

	fname = ""    #: The base name of the file
	location = "" #: An absolute path to the directory containing the file
	URI = ""      #: A URI where the actual file contents can be found
	contents = "" #: The full contents of the file - as configured in TO, not the on-disk contents
	sanitizedContents = "" #: Will store the contents after sanitization

	def __init__(self, raw:dict = None, toURL:str = "", tsroot:str = "/", *unused_args, contents: str = None, path: str = None):
		"""
		Constructs a :class:`ConfigFile` object from a raw API response

		:param raw: A raw config file from an API response
		:param toURL: The URL of a valid Traffic Ops host
		:param tsroot: The absolute path to the root of an Apache Traffic Server installation
		:param contents: Directly constructs a ConfigFile from the passed contents. Must be used with path, and causes raw to be ignored.
		:param path: Sets the full path to the file when constructing ConfigFiles directly from contents.
		:raises ValueError: if ``raw`` does not faithfully represent a configuration file

		>>> a = ConfigFile({"fnameOnDisk": "test",
		...                 "location": "/path/to",
		...                 "apiUri":"/test",
		...                 "scope": "servers"}, "http://example.com/")
		>>> a
		ConfigFile(path='/path/to/test', URI='http://example.com/test', scope='servers')
		>>> a.SSLdir
		'/etc/trafficserver/ssl'
		>>> ConfigFile(contents='testquest', path='/path/to/test')
		ConfigFile(path='/path/to/test', URI=None, scope=None)
		"""
		self.SSLdir = os.path.join(tsroot, "etc", "trafficserver", "ssl")

		if contents is not None:
			if path is None:
				raise ValueError("cannot construct from direct contents without setting path")
			self.location, self.fname = os.path.split(path)
			self.contents = contents
			self.scope = None
			return
		if raw is not None:
			try:
				self.fname = raw["fnameOnDisk"]
				self.location = raw["location"]
				if "apiUri" in raw:
					self.URI = toURL + raw["apiUri"].lstrip('/')
				else:
					self.URI = raw["url"]
				self.scope = raw["scope"]
			except (KeyError, TypeError, IndexError) as e:
				raise ValueError from e

	def __repr__(self) -> str:
		"""
		Implements ``repr(self)``

		>>> repr(ConfigFile({"fnameOnDisk": "test",
		...                  "location": "/path/to",
		...                  "apiUri": "test",
		...                  "scope": "servers"}, "http://example.com/"))
		"ConfigFile(path='/path/to/test', URI='http://example.com/test', scope='servers')"
		"""
		return "ConfigFile(path=%r, URI=%r, scope=%r)" %\
		          (self.path, self.URI if self.URI else None, self.scope)

	@property
	def path(self) -> str:
		"""
		The full path to the file on disk

		:returns: The system's path separator concatenating :attr:`location` and :attr`fname`
		"""
		return os.path.join(self.location, self.fname)

	def fetchContents(self, api:'to_api.API'):
		"""
		Fetches the file contents from :attr:`URI` if possible. Sets :attr:`contents` when
		successful.

		:param api: A valid, authenticated API session for use when interacting with Traffic Ops
		:raises ConnectionError: if something goes wrong fetching the file contents from Traffic
			Ops
		"""
		logging.info("Fetching file %s", self.fname)

		try:
			self.contents = api.getRaw(self.URI)
		except ValueError as e:
			raise ConnectionError from e

		logging.info("fetched")