"""
The main entry point to CLI usage of GAVO code.
"""

#c Copyright 2008-2024, the GAVO project <gavo@ari.uni-heidelberg.de>
#c
#c This program is free software, covered by the GNU GPL.  See the
#c COPYING file in the source distribution.


# The idea here is that you expose a CLI functionality by giving, as
# strings, the module and function to call.
#
# We also give a little startup note if we're running on a tty.
# While we do this, we import api; that should take care of most
# of the real startup time.

import os
import sys
import textwrap
import traceback

# this (and its companion in api.py) works around a race condition in
# Debian stretch's python-cryptography module.  Remove about 2021.
from cryptography.hazmat.bindings.openssl.binding import Binding #noflake: see above

from gavo.user import common


functions = [
	("admin", ("user.admin", "main")),
	("adql", ("protocols.adqlglue", "localquery")),
	("config", ("base.config", "main")),
	("datapack", ("protocols.datapack", "main")),
	("drop", ("user.dropping", "dropRD")),
	("dlrun", ("protocols.dlasync", "main")),
	("dump", ("rsc.dumping", "main")),
	("gencol", ("user.mkrd", "gencol")),
	("gendoc", ("user.docgen", "main")),
	("import", ("user.importing", "main")),
	("info", ("user.info", "main")),
	("limits", ("user.limits", "main")),
	("mkboost", ("grammars.directgrammar", "main")),
	("mkrd", ("user.mkrd", "main")),
	("publish", ("registry.publication", "main")),
	("purge", ("user.dropping", "dropTable")),
	("raise", ("user.errhandle", "bailOut")),
	("serve", ("user.serve", "main")),
	("start", ("user.mkrd", "start")),
	("stc", ("stc.cli", "main")),
	("show", ("user.show", "main")),
	("test", ("rscdef.regtest", "main")),
	("taprun", ("protocols.taprunner", "main")),
	("totesturl", ("rscdef.regtest", "urlToURL")),
	("validate", ("user.validation", "main")),
	("upgrade", ("user.upgrade", "main")),
	("uwsrun", ("protocols.useruws", "main")),
# init is special cased, but we want it in here for help generation
	("init", ("user.initdachs", "main")),
]


def _enablePDB():
# This can't be a callback to the --enable-pdb option since it needs
# errhandle, and we only want to import this after the command line
# is parsed
	import pdb
	def enterPdb(type, value, tb):
		traceback.print_exception(type, value, tb)
		pdb.pm()
	sys.excepthook = enterPdb
	from gavo.base import events
	events.PDB_ENABLED = True


def _enableDebug(*args):
	from gavo import base
	base.DEBUG = True


def _printVersion(*args):
	from gavo import base
	from gavo.user import upgrade
	print("Software (%s) Schema (%s/%s)"%(
		base.getVersion(),
		upgrade.CURRENT_SCHEMAVERSION,
		upgrade.getDBSchemaVersion()))
	sys.exit(0)


def _parseCLArgs():
	"""parses the command line and returns instructions on how to go on.

	As a side effect, sys.argv is manipulated such that the program
	called thinks it was execd in the first place.
	"""
	from optparse import OptionParser
	sels = [n for n,x in functions]
	sels.sort()
	parser = OptionParser(usage="%prog {<global option>} <func>"
		" {<func option>} {<func argument>}\n"+
		textwrap.fill("<func> is a unique prefix into {%s}"%(", ".join(sels)),
		initial_indent='', subsequent_indent='  '),
		description="Try %prog <func> --help for function-specific help")
	parser.disable_interspersed_args()
	parser.add_option("--traceback", help="print a traceback on all errors.",
		action="store_true", dest="alwaysTracebacks")
	parser.add_option("--hints", help="if there are hints on an error, display"
		" them", action="store_true", dest="showHints")
	parser.add_option("--enable-pdb", help="run pdb on all errors.",
		action="store_true", dest="enablePDB")
	parser.add_option("--disable-spew", help='Ignored.',
		action="store_true", dest="disableSpew")
	parser.add_option("--profile-to", metavar="PROFILEPATH",
		help="enable profiling and write a profile to PROFILEPATH",
		action="store", dest="profilePath", default=None)
	parser.add_option("--suppress-log", help="Do not log exceptions and such"
		" to the dachs-specific log files", action="store_true",
		dest="suppressLog")
	parser.add_option("--debug", help="Produce debug info as appropirate.",
		action="callback", callback=_enableDebug)
	parser.add_option("--version", help="Write software version to stdout"
		" and exit", action="callback", callback=_printVersion)
	parser.add_option("-U", "--ui", help="use UI to show what is going on;"
		" try --ui=help to see available interfaces.",
		dest="uiName", action="store", type="str", default="module-dependent",
		metavar="UI")

	opts, args = parser.parse_args()
	if len(args)<1:
		parser.print_help(file=sys.stderr)
		sys.exit(2)

	module, funcName = common.getMatchingFunction(args[0], functions, parser)
	parser.destroy()
	args[0] = "dachs "+args[0]
	sys.argv = args
	return opts, module, funcName


def main():

	# we want to preserve group-writability in all our operations;  hence
	# this prominent place for overriding a user decision...
	os.umask(0o02)

	# Special case: initial setup, no api working yet, and if things fail,
	# I cannot rely on normal DaCHS error reporting, which needs significant
	# parts of DaCHS.
	if len(sys.argv)>1 and sys.argv[1]=="init":
		try:
			del sys.argv[1]
			from gavo.user import initdachs
			sys.exit(initdachs.main())
		except Exception as exc:
			sys.stderr.write(f"Ouch -- Exception escaped from initdachs: {exc}\n")
			sys.exit(1)

	# Normal case: try executing what the user asked for and report
	# errors centrally.
	try:
		opts, module, funcName = _parseCLArgs()
		from gavo import base
		from gavo import utils
		from gavo.user import errhandle
		from gavo.user import plainui
		from gavo.user import useless

		interfaces = {
			"deluge": useless.DelugeUI,
			"null": useless.NullUI,
			"stingy": plainui.StingyPlainUI,
			"semistingy": plainui.SemiStingyPlainUI,
			"plain": plainui.PlainUI,
		}

		if not (opts.suppressLog or os.environ.get("GAVO_LOG")=="no"):
			from gavo.user import logui
			logui.LoggingUI(base.ui)

		if opts.uiName=="module-dependent":
			opts.uiName = {"registry.publication": "semistingy",
				"user.serve": "null",
				"user.validation": "null",
				}.get(module, "plain")
		if opts.uiName not in interfaces:
			raise base.ReportableError("UI %s does not exist.  Choose one of"
				" %s"%(opts.uiName, ", ".join(interfaces)))
		interfaces[opts.uiName](base.ui)

		if opts.enablePDB:
			_enablePDB()
		funcToRun = utils.loadInternalObject(module, funcName)

		if opts.profilePath:
			import cProfile
			cProfile.runctx("funcToRun()", globals(), locals(), opts.profilePath)
			return
	except Exception:
		from gavo.user import errhandle
		sys.exit(errhandle.raiseAndCatch())

	try:
		funcToRun()
	except Exception:
		if opts.alwaysTracebacks:
			traceback.print_exc()
		sys.exit(errhandle.raiseAndCatch(opts))


if __name__=="__main__":
	main()
